From a2367ef14f6c9b371d95f41d0758494e3f99fe14 Mon Sep 17 00:00:00 2001 From: unclejay <2KPKNc#6RaKl> Date: Mon, 16 Mar 2020 21:12:15 +0100 Subject: [PATCH 01/59] added network proxy configuration --- CHANGES.md | 2 +- .../im/vector/matrix/android/api/Matrix.kt | 4 +++- .../android/api/config/ProxyConfiguration.kt | 24 +++++++++++++++++++ .../android/internal/di/NetworkModule.kt | 9 ++++++- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt diff --git a/CHANGES.md b/CHANGES.md index d26237fd13..b1034f9ad6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - initialize with proxy configuration Build 🧱: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 22ac0324cf..4ebbeced8c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -23,6 +23,7 @@ import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.AuthenticationService +import im.vector.matrix.android.api.config.ProxyConfiguration import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt @@ -37,7 +38,8 @@ import javax.inject.Inject data class MatrixConfiguration( val applicationFlavor: String = "Default-application-flavor", - val cryptoConfig: MXCryptoConfig = MXCryptoConfig() + val cryptoConfig: MXCryptoConfig = MXCryptoConfig(), + val proxyConfig: ProxyConfiguration? = null ) { interface Provider { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt new file mode 100644 index 0000000000..b23ffa82f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.config + +import java.net.Proxy + +/** + * This is the configuration to use a proxy to connect to the matrix servers + */ +data class ProxyConfiguration(val hostname: String, val port: Int, val proxyType: Proxy.Type) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt index 4d6c66b7ed..a3ebeb5e11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt @@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides import im.vector.matrix.android.BuildConfig +import im.vector.matrix.android.api.MatrixConfiguration import im.vector.matrix.android.internal.network.TimeOutInterceptor import im.vector.matrix.android.internal.network.UserAgentInterceptor import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor @@ -28,6 +29,8 @@ import im.vector.matrix.android.internal.network.interceptors.FormattedJsonHttpL import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okreplay.OkReplayInterceptor +import java.net.InetSocketAddress +import java.net.Proxy import java.util.concurrent.TimeUnit @Module @@ -64,7 +67,8 @@ internal object NetworkModule { @Provides @JvmStatic @Unauthenticated - fun providesOkHttpClient(stethoInterceptor: StethoInterceptor, + fun providesOkHttpClient(matrixConfiguration: MatrixConfiguration, + stethoInterceptor: StethoInterceptor, timeoutInterceptor: TimeOutInterceptor, userAgentInterceptor: UserAgentInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor, @@ -82,6 +86,9 @@ internal object NetworkModule { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) } + matrixConfiguration.proxyConfig?.let { + proxy(Proxy(it.proxyType, InetSocketAddress(it.hostname, it.port))) + } } .addInterceptor(okReplayInterceptor) .build() From aa16ba88ae36a763a26b166d2595e7eb1be87a43 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 Apr 2020 12:41:47 +0200 Subject: [PATCH 02/59] Add hint to translators --- vector/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 370b7cf8f4..93638420e7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2124,6 +2124,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Verify this session Other users may not trust it + Complete Security Use an existing session to verify this one, granting it access to encrypted messages. From 2c47fe9f0d959f2d9e97af6d7b8c2b7f69635669 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Apr 2020 11:47:33 +0200 Subject: [PATCH 03/59] typo --- .../session/room/timeline/DefaultTimeline.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index f2bee734ce..82729f092d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -383,7 +383,7 @@ internal class DefaultTimeline( } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results * @return true if createSnapshot should be posted */ private fun paginateInternal(startDisplayIndex: Int?, @@ -446,7 +446,7 @@ internal class DefaultTimeline( } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ private fun handleInitialLoad() { var shouldFetchInitialEvent = false @@ -478,7 +478,7 @@ internal class DefaultTimeline( } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ private fun handleUpdates(results: RealmResults, changeSet: OrderedCollectionChangeSet) { // If changeSet has deletion we are having a gap, so we clear everything @@ -516,7 +516,7 @@ internal class DefaultTimeline( } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) @@ -560,23 +560,22 @@ internal class DefaultTimeline( } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ - private fun getTokenLive(direction: Timeline.Direction): String? { val chunkEntity = getLiveChunk() ?: return null return if (direction == Timeline.Direction.BACKWARDS) chunkEntity.prevToken else chunkEntity.nextToken } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ private fun getLiveChunk(): ChunkEntity? { return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull() } /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results * @return number of items who have been added */ private fun buildTimelineEvents(startDisplayIndex: Int?, @@ -628,7 +627,7 @@ internal class DefaultTimeline( ) /** - * This has to be called on TimelineThread as it access realm live results + * This has to be called on TimelineThread as it accesses realm live results */ private fun getOffsetResults(startDisplayIndex: Int, direction: Timeline.Direction, From 2697800deb7a60000ae13707710a170ebd9c7127 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Apr 2020 11:48:40 +0200 Subject: [PATCH 04/59] Doc and cleanup --- .../android/internal/database/helper/ChunkEntityHelper.kt | 7 +++---- .../matrix/android/internal/database/model/ChunkEntity.kt | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) 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 80376fb6ee..d86151e694 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 @@ -60,10 +60,9 @@ internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direct chunkToMerge.stateEvents.forEach { stateEvent -> addStateEvent(roomId, stateEvent, direction) } - return eventsToMerge - .forEach { - addTimelineEventFromMerge(localRealm, it, direction) - } + eventsToMerge.forEach { + addTimelineEventFromMerge(localRealm, it, direction) + } } internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, direction: PaginationDirection) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt index 2d294e6783..9c6bf5f757 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt @@ -23,9 +23,11 @@ import io.realm.annotations.Index import io.realm.annotations.LinkingObjects internal open class ChunkEntity(@Index var prevToken: String? = null, + // Because of gaps we can have several chunks with nextToken == null @Index var nextToken: String? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), + // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, @Index var isLastBackward: Boolean = false ) : RealmObject() { From 7e955ef0e4e1b1f0cad74e580dbbe6de33b0452a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Apr 2020 11:49:08 +0200 Subject: [PATCH 05/59] Add possibility to create clear room --- .../matrix/android/common/CryptoTestHelper.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 9278bed918..e4aa7872aa 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -53,17 +53,19 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { /** * @return alice session */ - fun doE2ETestWithAliceInARoom(): CryptoTestData { + fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = mTestHelper.doSync { aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it) } - val room = aliceSession.getRoom(roomId)!! + if (encryptedRoom) { + val room = aliceSession.getRoom(roomId)!! - mTestHelper.doSync { - room.enableEncryption(callback = it) + mTestHelper.doSync { + room.enableEncryption(callback = it) + } } return CryptoTestData(aliceSession, roomId) @@ -72,8 +74,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { /** * @return alice and bob sessions */ - fun doE2ETestWithAliceAndBobInARoom(): CryptoTestData { - val cryptoTestData = doE2ETestWithAliceInARoom() + fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData { + val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId From bfd847179f8b55e77ad7759ac0eff85b56f460eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Apr 2020 11:49:42 +0200 Subject: [PATCH 06/59] Wait more --- .../java/im/vector/matrix/android/common/CommonTestHelper.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 5bc8653f3d..ad12fb6bba 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -143,7 +143,8 @@ class CommonTestHelper(context: Context) { for (i in 0 until nbOfMessages) { room.sendTextMessage(message + " #" + (i + 1)) } - await(latch) + // Wait 3 second more per message + await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages) timeline.removeListener(timelineListener) timeline.dispose() From 20b726819ff8136f1dd2aa5e3ffce099166dc85f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Apr 2020 17:35:48 +0200 Subject: [PATCH 07/59] Rename "LastLive" -> "LastForward" --- .../android/internal/database/query/ChunkEntityQueries.kt | 2 +- .../matrix/android/internal/database/query/ReadQueries.kt | 2 +- .../internal/database/query/TimelineEventEntityQueries.kt | 2 +- .../session/room/timeline/TokenChunkEventPersistor.kt | 8 ++++---- .../android/internal/session/sync/RoomSyncHandler.kt | 5 +++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt index 009ee4b7fe..5efb84a105 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ChunkEntityQueries.kt @@ -41,7 +41,7 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: return query.findFirst() } -internal fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomId: String): ChunkEntity? { +internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .findFirst() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt index 1b83577a8c..9c73dff1dd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt @@ -36,7 +36,7 @@ internal fun isEventRead(monarchy: Monarchy, var isEventRead = false monarchy.doWithRealm { realm -> - val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) ?: return@doWithRealm + val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@doWithRealm val eventToCheck = liveChunk.timelineEvents.find(eventId) isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { true diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 5168d0728e..c3e9a8288b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -59,7 +59,7 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, filterTypes: List = emptyList()): TimelineEventEntity? { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes) - val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) + val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) if (filterContentRelation) { liveEvents ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE) 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 164626224b..f6fd88f816 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 @@ -35,7 +35,7 @@ import im.vector.matrix.android.internal.database.query.copyToRealmOrIgnore 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.findLastLiveChunkFromRoom +import im.vector.matrix.android.internal.database.query.findLastForwardChunkOfRoom import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.where @@ -169,10 +169,10 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { Timber.v("Reach end of $roomId") if (direction == PaginationDirection.FORWARDS) { - val currentLiveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) - if (currentChunk != currentLiveChunk) { + val currentLastForwardChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) + if (currentChunk != currentLastForwardChunk) { currentChunk.isLastForward = true - currentLiveChunk?.deleteOnCascade() + currentLastForwardChunk?.deleteOnCascade() RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { latestPreviewableEvent = TimelineEventEntity.latestEvent( realm, 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 70c1e39334..8c21d23a8c 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 @@ -36,7 +36,7 @@ import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.copyToRealmOrIgnore 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.findLastForwardChunkOfRoom import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.where @@ -220,12 +220,13 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle prevToken: String? = null, isLimited: Boolean = true, syncLocalTimestampMillis: Long): ChunkEntity { - val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId) + val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk } else { realm.createObject().apply { this.prevToken = prevToken } } + // Only one chunk has isLastForward set to true lastChunk?.isLastForward = false chunkEntity.isLastForward = true From a61434ae0877d829d51eb72582606a25517415e9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Apr 2020 17:37:58 +0200 Subject: [PATCH 08/59] doc --- .../vector/matrix/android/api/session/room/timeline/Timeline.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt index d7d6682046..19ff65dbe2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt @@ -58,7 +58,7 @@ interface Timeline { /** * Check if the timeline can be enriched by paginating. - * @param the direction to check in + * @param direction the direction to check in * @return true if timeline can be enriched */ fun hasMoreToLoad(direction: Direction): Boolean From becc5a7b541680d65ff2b4acf6de4def0cba199f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Apr 2020 17:39:01 +0200 Subject: [PATCH 09/59] Add assertion in debug --- .../android/api/session/room/timeline/TimelineEvent.kt | 7 +++++++ 1 file changed, 7 insertions(+) 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 0c8a04db36..7adc438e20 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 @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.room.timeline +import im.vector.matrix.android.BuildConfig 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 @@ -45,6 +46,12 @@ data class TimelineEvent( val readReceipts: List = emptyList() ) { + init { + if (BuildConfig.DEBUG) { + assert(eventId == root.eventId) + } + } + val metadata = HashMap() /** From 8966e249256d47fd0e81767df9a883d3637b7420 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Apr 2020 18:23:18 +0200 Subject: [PATCH 10/59] Create a debug method to send x times the same event --- .../session/room/send/DefaultSendService.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 1037b7c79c..eee1c01295 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -74,6 +74,19 @@ internal class DefaultSendService @AssistedInject constructor( return sendEvent(event) } + // For test only + private fun sendTextMessages(text: CharSequence, msgType: String, autoMarkdown: Boolean, times: Int): Cancelable { + return CancelableBag().apply { + // Send the event several times + repeat(times) { i -> + val event = localEchoEventFactory.createTextEvent(roomId, msgType, "$text - $i", autoMarkdown).also { + createLocalEcho(it) + } + add(sendEvent(event)) + } + } + } + override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable { val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType).also { createLocalEcho(it) From f3c3c07d468e04820cc1e465aec1bc5410aef40f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 30 Apr 2020 18:36:43 +0200 Subject: [PATCH 11/59] Kotlin sugar --- .../session/room/send/DefaultSendService.kt | 77 +++++++++---------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index eee1c01295..9c8723af05 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -68,10 +68,9 @@ internal class DefaultSendService @AssistedInject constructor( private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor() override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable { - val event = localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown).also { - createLocalEcho(it) - } - return sendEvent(event) + return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown) + .also { createLocalEcho(it) } + .let { sendEvent(it) } } // For test only @@ -79,33 +78,30 @@ internal class DefaultSendService @AssistedInject constructor( return CancelableBag().apply { // Send the event several times repeat(times) { i -> - val event = localEchoEventFactory.createTextEvent(roomId, msgType, "$text - $i", autoMarkdown).also { - createLocalEcho(it) - } - add(sendEvent(event)) + localEchoEventFactory.createTextEvent(roomId, msgType, "$text - $i", autoMarkdown) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + .also { add(it) } } } } override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable { - val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType).also { - createLocalEcho(it) - } - return sendEvent(event) + return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType) + .also { createLocalEcho(it) } + .let { sendEvent(it) } } override fun sendPoll(question: String, options: List): Cancelable { - val event = localEchoEventFactory.createPollEvent(roomId, question, options).also { - createLocalEcho(it) - } - return sendEvent(event) + return localEchoEventFactory.createPollEvent(roomId, question, options) + .also { createLocalEcho(it) } + .let { sendEvent(it) } } override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable { - val event = localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue).also { - createLocalEcho(it) - } - return sendEvent(event) + return localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue) + .also { createLocalEcho(it) } + .let { sendEvent(it) } } private fun sendEvent(event: Event): Cancelable { @@ -132,8 +128,8 @@ internal class DefaultSendService @AssistedInject constructor( override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? - val redactWork = createRedactEventWork(event, reason) - return timelineSendEventWorkCommon.postWork(roomId, redactWork) + return createRedactEventWork(event, reason) + .let { timelineSendEventWorkCommon.postWork(roomId, it) } } override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { @@ -276,31 +272,30 @@ internal class DefaultSendService @AssistedInject constructor( private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { // Same parameter - val params = EncryptEventWorker.Params(sessionId, event) - val sendWorkData = WorkerParamsFactory.toData(params) - - return workManagerProvider.matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerProvider.workConstraints) - .setInputData(sendWorkData) - .startChain(startChain) - .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS) - .build() + return EncryptEventWorker.Params(sessionId, event) + .let { WorkerParamsFactory.toData(it) } + .let { + workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) + .setInputData(it) + .startChain(startChain) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .build() + } } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(sessionId, event) - val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - - return timelineSendEventWorkCommon.createWork(sendWorkData, startChain) + return SendEventWorker.Params(sessionId, event) + .let { WorkerParamsFactory.toData(it) } + .let { timelineSendEventWorkCommon.createWork(it, startChain) } } private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest { - val redactEvent = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason).also { - createLocalEcho(it) - } - val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason) - val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - return timelineSendEventWorkCommon.createWork(redactWorkData, true) + return localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) + .also { createLocalEcho(it) } + .let { RedactEventWorker.Params(sessionId, it.eventId!!, roomId, event.eventId, reason) } + .let { WorkerParamsFactory.toData(it) } + .let { timelineSendEventWorkCommon.createWork(it, true) } } private fun createUploadMediaWork(allLocalEchos: List, From 86fba283131ab9b5315694ece8af42ba9f6bde78 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 5 May 2020 02:40:50 +0200 Subject: [PATCH 12/59] After jump to unread, newer messages are never loaded (#1008) --- CHANGES.md | 2 +- .../internal/database/model/ChunkEntity.kt | 3 +++ .../session/room/timeline/DefaultTimeline.kt | 27 ++++++++++++++++++- .../room/timeline/TokenChunkEventPersistor.kt | 9 ++++++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f0cf30af2d..bc04df2e39 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - After jump to unread, newer messages are never loaded (#1008) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt index 9c6bf5f757..19bf72970c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ChunkEntity.kt @@ -34,6 +34,9 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, fun identifier() = "${prevToken}_$nextToken" + // If true, then this chunk was previously a last forward chunk + fun hasBeenALastForwardChunk() = nextToken == null && !isLastForward + @LinkingObjects("chunks") val room: RealmResults? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 82729f092d..8cfd498cc8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -559,6 +559,28 @@ internal class DefaultTimeline( .executeBy(taskExecutor) } + // For debug purpose only + private fun dumpAndLogChunks() { + val liveChunk = getLiveChunk() + Timber.w("Live chunk: $liveChunk") + + Realm.getInstance(realmConfiguration).use { realm -> + ChunkEntity.where(realm, roomId).findAll() + .also { Timber.w("Found ${it.size} chunks") } + .forEach { + Timber.w("") + Timber.w("ChunkEntity: $it") + Timber.w("prevToken: ${it.prevToken}") + Timber.w("nextToken: ${it.nextToken}") + Timber.w("isLastBackward: ${it.isLastBackward}") + Timber.w("isLastForward: ${it.isLastForward}") + it.timelineEvents.forEach { tle -> + Timber.w(" TLE: ${tle.root?.content}") + } + } + } + } + /** * This has to be called on TimelineThread as it accesses realm live results */ @@ -569,6 +591,7 @@ internal class DefaultTimeline( /** * This has to be called on TimelineThread as it accesses realm live results + * Return the current Chunk */ private fun getLiveChunk(): ChunkEntity? { return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull() @@ -576,7 +599,7 @@ internal class DefaultTimeline( /** * This has to be called on TimelineThread as it accesses realm live results - * @return number of items who have been added + * @return the number of items who have been added */ private fun buildTimelineEvents(startDisplayIndex: Int?, direction: Timeline.Direction, @@ -617,6 +640,8 @@ internal class DefaultTimeline( } val time = System.currentTimeMillis() - start Timber.v("Built ${offsetResults.size} items from db in $time ms") + // For the case where wo reach the lastForward chunk + updateLoadingStates(filteredEvents) return offsetResults.size } 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 f6fd88f816..6161e8e5fb 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 @@ -224,11 +224,18 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) } + // Find all the chunks which contain at least one event from the list of eventIds val chunks = ChunkEntity.findAllIncludingEvents(realm, eventIds) + Timber.d("Found ${chunks.size} chunks containing at least one of the eventIds") val chunksToDelete = ArrayList() chunks.forEach { if (it != currentChunk) { - currentChunk.merge(roomId, it, direction) + if (direction == PaginationDirection.FORWARDS && it.hasBeenALastForwardChunk()) { + Timber.d("Do not merge $it") + } else { + Timber.d("Merge $it") + currentChunk.merge(roomId, it, direction) + } chunksToDelete.add(it) } } From 697eaec197b338e735b48095c7b5424e9df67908 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 1 May 2020 00:36:01 +0200 Subject: [PATCH 13/59] TI: After jump to unread, newer messages are never loaded (#1008) --- .../matrix/android/common/CommonTestHelper.kt | 24 ++- .../timeline/TimelineForwardPaginationTest.kt | 175 ++++++++++++++++++ 2 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index ad12fb6bba..2529be9547 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -28,10 +28,10 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings @@ -116,7 +116,7 @@ class CommonTestHelper(context: Context) { */ fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List { val sentEvents = ArrayList(nbOfMessages) - val latch = CountDownLatch(nbOfMessages) + val latch = CountDownLatch(1) val timelineListener = object : Timeline.Listener { override fun onTimelineFailure(throwable: Throwable) { } @@ -127,7 +127,7 @@ class CommonTestHelper(context: Context) { override fun onTimelineUpdated(snapshot: List) { val newMessages = snapshot - .filter { LocalEcho.isLocalEchoId(it.eventId).not() } + .filter { it.root.sendState == SendState.SYNCED } .filter { it.root.getClearType() == EventType.MESSAGE } .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true } @@ -292,6 +292,24 @@ class CommonTestHelper(context: Context) { return requestFailure!! } + fun createEventListener(latch: CountDownLatch, predicate: (List) -> Boolean): Timeline.Listener { + return object : Timeline.Listener { + override fun onTimelineFailure(throwable: Throwable) { + // noop + } + + override fun onNewTimelineEvents(eventIds: List) { + // noop + } + + override fun onTimelineUpdated(snapshot: List) { + if (predicate(snapshot)) { + latch.countDown() + } + } + } + } + /** * Await for a latch and ensure the result is true * diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt new file mode 100644 index 0000000000..1540f9f99b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2020 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.session.room.timeline + +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import timber.log.Timber +import java.util.concurrent.CountDownLatch + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelineForwardPaginationTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + /** + * This test ensure that if we click to permalink, we will be able to go back to the live + */ + @Test + fun forwardPaginationTest() { + val numberOfMessagesToSend = 90 + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) + + val aliceSession = cryptoTestData.firstSession + val aliceRoomId = cryptoTestData.roomId + + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + + // Alice sends X messages + val sentMessages = commonTestHelper.sendTextMessage( + roomFromAlicePOV, + "Message from Alice, long enough to observe the problem, if it is not long enough, there is not always the problem", + numberOfMessagesToSend) + + // Alice clear the cache + commonTestHelper.doSync { + aliceSession.clearCache(it) + } + + aliceSession.startSync(true) + + val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) + aliceTimeline.start() + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Alice timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root.content}") + } + + // Ok, we have the 10 first messages of the initial sync + snapshot.size == 10 + } + + // Open the timeline at last sent message + aliceTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + aliceTimeline.removeAllListeners() + + aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + run { + val lock = CountDownLatch(1) + val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Alice timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root.content}") + } + + // The event is not in db, so it is fetch alone + snapshot.size == 1 + } + + aliceTimeline.addListener(aliceEventsListener) + + // Restart the timeline to the first sent event + aliceTimeline.restartWithEventId(sentMessages.last().eventId) + + commonTestHelper.await(lock) + aliceTimeline.removeAllListeners() + + aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + } + + run { + val lock = CountDownLatch(1) + val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Alice timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root.content}") + } + + // Alice can see the first event of the room (so Back pagination has worked) + snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE + // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination + && snapshot.size == 6 + 1 + 50 + } + + aliceTimeline.addListener(aliceEventsListener) + + // Restart the timeline to the first sent event + // We ask to load event backward and forward + aliceTimeline.paginate(Timeline.Direction.BACKWARDS, 50) + aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + aliceTimeline.removeAllListeners() + + aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + run { + val lock = CountDownLatch(1) + val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Alice timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root.content}") + } + + // 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) + snapshot.size == 6 + numberOfMessagesToSend + } + + aliceTimeline.addListener(aliceEventsListener) + + // Ask for a forward pagination + aliceTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + aliceTimeline.removeAllListeners() + + // The timeline is fully loaded + aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + aliceTimeline.dispose() + + cryptoTestData.cleanUp(commonTestHelper) + } +} From 92befcde5d747a5f22c63a0e1ecf9af3920b5d1f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 1 May 2020 01:45:06 +0200 Subject: [PATCH 14/59] Add test to cover previous last forward case (passing) --- .../TimelinePreviousLastForwardTest.kt | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt new file mode 100644 index 0000000000..7ae307ec8b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2020 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.session.room.timeline + +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.extensions.orFalse +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.message.MessageContent +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import timber.log.Timber +import java.util.concurrent.CountDownLatch + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelinePreviousLastForwardTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + /** + * This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink, we will be able to go back to the live + */ + @Test + fun previousLastForwardTest() { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession!! + val aliceRoomId = cryptoTestData.roomId + + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + bobSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + bobTimeline.start() + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Ok, we have the 8 first messages of the initial sync (room creation and bob join event) + snapshot.size == 8 + } + + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + // Bob stop to sync + bobSession.stopSync() + + // Alice sends 30 messages + val firstMessageFromAliceId = commonTestHelper.sendTextMessage( + roomFromAlicePOV, + "First messages from Alice", + 30) + .last() + .eventId + + // Bob start to sync + bobSession.startSync(true) + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk + snapshot.size == 10 + && snapshot.all { it.root.content.toModel()?.body?.startsWith("First messages from Alice").orFalse() } + } + + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + // Bob stop to sync + bobSession.stopSync() + + // Alice sends again 30 messages + commonTestHelper.sendTextMessage( + roomFromAlicePOV, + "Second messages from Alice", + 30) + + // Bob start to sync + bobSession.startSync(true) + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk + snapshot.size == 10 + && snapshot.all { it.root.content.toModel()?.body?.startsWith("Second messages from Alice").orFalse() } + } + + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + // Bob navigate to the first message sent from Alice + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // The event is not in db, so it is fetch + snapshot.size == 1 + } + + bobTimeline.addListener(eventsListener) + + // Restart the timeline to the first sent event, and paginate in both direction + bobTimeline.restartWithEventId(firstMessageFromAliceId) + bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50) + bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + } + + // Paginate in both direction + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + snapshot.size == 8 + 1 + 35 + } + + bobTimeline.addListener(eventsListener) + + // Paginate in both direction + bobTimeline.paginate(Timeline.Direction.BACKWARDS, 50) + // Ensure the chunk in the middle is included in the next pagination + bobTimeline.paginate(Timeline.Direction.FORWARDS, 35) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + // Bob scroll to the future, till the live + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Bob can see the first event of the room (so Back pagination has worked) + snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE + // 8 for room creation item 60 message from Alice + && snapshot.size == 8 + 60 + } + + bobTimeline.addListener(eventsListener) + + bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + bobTimeline.dispose() + + cryptoTestData.cleanUp(commonTestHelper) + } +} From 2b9d3960b384279662d43665d6ba189750eeb8a2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 May 2020 15:02:09 +0200 Subject: [PATCH 15/59] Improve tests --- .../timeline/TimelineForwardPaginationTest.kt | 17 +++++++++++++++-- .../timeline/TimelinePreviousLastForwardTest.kt | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt index 1540f9f99b..ae6a9f8d42 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt @@ -17,7 +17,10 @@ package im.vector.matrix.android.session.room.timeline import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.extensions.orFalse 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.message.MessageContent import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.common.CommonTestHelper @@ -57,7 +60,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { // Alice sends X messages val sentMessages = commonTestHelper.sendTextMessage( roomFromAlicePOV, - "Message from Alice, long enough to observe the problem, if it is not long enough, there is not always the problem", + "Message from Alice", numberOfMessagesToSend) // Alice clear the cache @@ -65,11 +68,13 @@ class TimelineForwardPaginationTest : InstrumentedTest { aliceSession.clearCache(it) } + // And restarts the sync aliceSession.startSync(true) val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) aliceTimeline.start() + // Alice sees the 10 last message of the room, and can only navigate BACKWARD run { val lock = CountDownLatch(1) val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -78,8 +83,9 @@ class TimelineForwardPaginationTest : InstrumentedTest { Timber.w(" event ${it.root.content}") } - // Ok, we have the 10 first messages of the initial sync + // Ok, we have the 10 last messages of the initial sync snapshot.size == 10 + && snapshot.all { it.root.content.toModel()?.body?.startsWith("Message from Alice").orFalse() } } // Open the timeline at last sent message @@ -91,6 +97,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { aliceTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() } + // Alice navigates to the first message of the room, which is not in its database. A GET /context is performed + // Then she can paginate BACKWARD and FORWARD run { val lock = CountDownLatch(1) val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -101,6 +109,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { // The event is not in db, so it is fetch alone snapshot.size == 1 + && snapshot.all { it.root.content.toModel()?.body?.startsWith("Message from Alice").orFalse() } } aliceTimeline.addListener(aliceEventsListener) @@ -115,6 +124,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() } + // Alice paginates BACKWARD and FORWARD of 50 events each + // Then she can only navigate FORWARD run { val lock = CountDownLatch(1) val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> @@ -143,6 +154,8 @@ class TimelineForwardPaginationTest : InstrumentedTest { aliceTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() } + // Alice paginates once again FORWARD for 50 events + // All the timeline is retrieved, she cannot paginate anymore in both direction run { val lock = CountDownLatch(1) val aliceEventsListener = commonTestHelper.createEventListener(lock) { snapshot -> diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt index 7ae307ec8b..0ca5cfdda0 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -70,7 +70,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { Timber.w(" event ${it.root}") } - // Ok, we have the 8 first messages of the initial sync (room creation and bob join event) + // Ok, we have the 8 first messages of the initial sync (room creation and bob invite and join events) snapshot.size == 8 } From 53583c691f9e4da12aab493b943df1f2ed801c60 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 May 2020 17:15:01 +0200 Subject: [PATCH 16/59] Add some logs --- .../internal/crypto/store/db/RealmCryptoStoreMigration.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index c1897c76d9..6ff8a49e28 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -196,6 +196,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi } private fun migrateTo3(realm: DynamicRealm) { + Timber.d("Step 2 -> 3") Timber.d("Updating CryptoMetadataEntity table") realm.schema.get("CryptoMetadataEntity") ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java) @@ -203,6 +204,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi } private fun migrateTo4(realm: DynamicRealm) { + Timber.d("Step 3 -> 4") Timber.d("Updating KeyInfoEntity table") val keyInfoEntities = realm.where("KeyInfoEntity").findAll() try { @@ -217,6 +219,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi } private fun migrateTo5(realm: DynamicRealm) { + Timber.d("Step 4 -> 5") realm.schema.create("MyDeviceLastSeenInfoEntity") .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java) .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID) From 17ddb5ce43fedebe818e5e15e0fa60063ca65451 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 May 2020 18:19:49 +0200 Subject: [PATCH 17/59] if all events are rendered in the timeline (developer mode), render the room creation event. --- .../src/main/res/values/strings.xml | 1 + .../timeline/factory/RoomCreateItemFactory.kt | 24 ++++++++++++------- .../timeline/factory/TimelineItemFactory.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 8 +++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 50169fd982..69907e5835 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ %1$s sent a sticker. %s\'s invitation + %1$s created the room %1$s invited %2$s %1$s invited you %1$s joined the room diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index bf3b82ab4d..d5471d7f4f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -21,21 +21,21 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R -import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.home.room.detail.timeline.item.RoomCreateItem import im.vector.riotx.features.home.room.detail.timeline.item.RoomCreateItem_ import me.gujun.android.span.span import javax.inject.Inject -class RoomCreateItemFactory @Inject constructor(private val colorProvider: ColorProvider, - private val stringProvider: StringProvider) { +class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider, + private val noticeItemFactory: NoticeItemFactory) { - fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): RoomCreateItem? { - val createRoomContent = event.root.getClearContent().toModel() - ?: return null - val predecessorId = createRoomContent.predecessor?.roomId ?: return null + fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + val createRoomContent = event.root.getClearContent().toModel() ?: return null + val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(event, callback) val roomLink = PermalinkFactory.createPermalink(predecessorId) ?: return null val text = span { +stringProvider.getString(R.string.room_tombstone_continuation_description) @@ -48,4 +48,12 @@ class RoomCreateItemFactory @Inject constructor(private val colorProvider: Color return RoomCreateItem_() .text(text) } + + private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + return if (userPreferencesProvider.shouldShowHiddenEvents()) { + noticeItemFactory.create(event, false, callback) + } else { + null + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 7e6c387934..f2ac7018aa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -58,7 +58,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.REACTION, - EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) + EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_ENCRYPTION -> { encryptionItemFactory.create(event, highlight, callback) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 39e17b7c35..f29bd72e0a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent +import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent @@ -47,6 +48,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) @@ -98,6 +100,12 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active return "{ \"type\": ${event.getClearType()} }" } + private fun formatRoomCreateEvent(event: Event): CharSequence? { + return event.getClearContent().toModel() + ?.takeIf { it.creator.isNullOrBlank().not() } + ?.let { sp.getString(R.string.notice_room_created, it.creator) } + } + private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (content.name.isNullOrBlank()) { From fcee85a6829557ac4169f04b137718493e6e7a47 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 5 May 2020 00:19:40 +0200 Subject: [PATCH 18/59] Cleanup and doc --- .../riotx/core/extensions/Collections.kt | 20 +++++++++++++++++++ .../timeline/TimelineEventController.kt | 2 +- .../factory/MergedHeaderItemFactory.kt | 14 ++++++------- .../helper/TimelineDisplayableEvents.kt | 8 -------- 4 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/extensions/Collections.kt diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Collections.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Collections.kt new file mode 100644 index 0000000000..af5d5babb6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Collections.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 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.extensions + +inline fun List.nextOrNull(index: Int) = getOrNull(index + 1) +inline fun List.prevOrNull(index: Int) = getOrNull(index - 1) 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 addbfab43c..e074af1da6 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 @@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.LoadingItem_ import im.vector.riotx.core.extensions.localDateTime +import im.vector.riotx.core.extensions.nextOrNull import im.vector.riotx.features.home.room.detail.RoomDetailAction import im.vector.riotx.features.home.room.detail.RoomDetailViewState import im.vector.riotx.features.home.room.detail.UnreadState @@ -45,7 +46,6 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.ReadMarkerVisib import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.riotx.features.home.room.detail.timeline.helper.nextOrNull import im.vector.riotx.features.home.room.detail.timeline.item.BaseEventItem import im.vector.riotx.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 03c273800a..ec6a975178 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent -import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.extensions.prevOrNull import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider @@ -37,15 +37,15 @@ import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreatio import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem_ import javax.inject.Inject -class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: ActiveSessionHolder, - private val avatarRenderer: AvatarRenderer, +class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider) { private val collapsedEventIds = linkedSetOf() private val mergeItemCollapseStates = HashMap() /** - * Note: nextEvent is an older event than event + * @param nextEvent is an older event than event + * @param items all known items, sorted from newer event to oldest event */ fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -127,9 +127,9 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedRoomCreationItem_? { - var prevEvent = if (currentPosition > 0) items[currentPosition - 1] else null + var prevEvent = items.prevOrNull(currentPosition) var tmpPos = currentPosition - 1 - val mergedEvents = ArrayList().also { it.add(event) } + val mergedEvents = mutableListOf(event) var hasEncryption = false var encryptionAlgorithm: String? = null while (prevEvent != null && prevEvent.isRoomConfiguration(null)) { @@ -139,7 +139,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act } mergedEvents.add(prevEvent) tmpPos-- - prevEvent = if (tmpPos >= 0) items[tmpPos] else null + prevEvent = items.getOrNull(tmpPos) } return if (mergedEvents.size > 2) { var highlighted = false diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index f1106d276e..daf0100bbb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -106,11 +106,3 @@ fun List.prevSameTypeEvents(index: Int, minSize: Int): List.nextOrNull(index: Int): TimelineEvent? { - return if (index >= size - 1) { - null - } else { - subList(index + 1, this.size).firstOrNull() - } -} From db77e7b8178dcc3aab09b29b10dd9c31cf50e357 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 5 May 2020 00:30:49 +0200 Subject: [PATCH 19/59] Create a fun --- .../factory/MergedHeaderItemFactory.kt | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index ec6a975178..9529693e6b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -64,60 +64,69 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av } else if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) { null } else { - val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2) - if (prevSameTypeEvents.isEmpty()) { - null - } else { - var highlighted = false - val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() - val mergedData = ArrayList(mergedEvents.size) - mergedEvents.forEach { mergedEvent -> - if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { - highlighted = true - } - val senderAvatar = mergedEvent.senderAvatar - val senderName = mergedEvent.getDisambiguatedDisplayName() - val data = BasedMergedItem.Data( - userId = mergedEvent.root.senderId ?: "", - avatarUrl = senderAvatar, - memberName = senderName, - localId = mergedEvent.localId, - eventId = mergedEvent.root.eventId ?: "" - ) - mergedData.add(data) + buildMembershipEventsMergedSummary(currentPosition, items, event, eventIdToHighlight, requestModelBuild, callback) + } + } + + private fun buildMembershipEventsMergedSummary(currentPosition: Int, + items: List, + event: TimelineEvent, + eventIdToHighlight: String?, + requestModelBuild: () -> Unit, + callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { + val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2) + return if (prevSameTypeEvents.isEmpty()) { + null + } else { + var highlighted = false + val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() + val mergedData = ArrayList(mergedEvents.size) + mergedEvents.forEach { mergedEvent -> + if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { + highlighted = true } - val mergedEventIds = mergedEvents.map { it.localId } - // We try to find if one of the item id were used as mergeItemCollapseStates key - // => handle case where paginating from mergeable events and we get more - val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() - val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) - ?: true - val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } - if (isCollapsed) { - collapsedEventIds.addAll(mergedEventIds) - } else { - collapsedEventIds.removeAll(mergedEventIds) - } - val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - val attributes = MergedMembershipEventsItem.Attributes( - isCollapsed = isCollapsed, - mergeData = mergedData, - avatarRenderer = avatarRenderer, - onCollapsedStateChanged = { - mergeItemCollapseStates[event.localId] = it - requestModelBuild() - }, - readReceiptsCallback = callback + val senderAvatar = mergedEvent.senderAvatar + val senderName = mergedEvent.getDisambiguatedDisplayName() + val data = BasedMergedItem.Data( + userId = mergedEvent.root.senderId ?: "", + avatarUrl = senderAvatar, + memberName = senderName, + localId = mergedEvent.localId, + eventId = mergedEvent.root.eventId ?: "" ) - MergedMembershipEventsItem_() - .id(mergeId) - .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(isCollapsed && highlighted) - .attributes(attributes) - .also { - it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) - } + mergedData.add(data) } + val mergedEventIds = mergedEvents.map { it.localId } + // We try to find if one of the item id were used as mergeItemCollapseStates key + // => handle case where paginating from mergeable events and we get more + val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() + val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) + ?: true + val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } + if (isCollapsed) { + collapsedEventIds.addAll(mergedEventIds) + } else { + collapsedEventIds.removeAll(mergedEventIds) + } + val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } + val attributes = MergedMembershipEventsItem.Attributes( + isCollapsed = isCollapsed, + mergeData = mergedData, + avatarRenderer = avatarRenderer, + onCollapsedStateChanged = { + mergeItemCollapseStates[event.localId] = it + requestModelBuild() + }, + readReceiptsCallback = callback + ) + MergedMembershipEventsItem_() + .id(mergeId) + .leftGuideline(avatarSizeProvider.leftGuideline) + .highlighted(isCollapsed && highlighted) + .attributes(attributes) + .also { + it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) + } } } From ffeae7ec83ffbed426ef3982e0b84501f7528ece Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 5 May 2020 02:38:30 +0200 Subject: [PATCH 20/59] Fix timeline navigation when opening an event in a previous lastForward chunk. In this case, we do not have a nextToken, but there are more event to load. So we perform a GET /context on the last known event. Not sure it is correct to do that though... --- .../TimelineBackToPreviousLastForwardTest.kt | 206 ++++++++++++++++++ .../session/room/timeline/DefaultTimeline.kt | 6 + .../room/timeline/TokenChunkEventPersistor.kt | 16 +- 3 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt new file mode 100644 index 0000000000..d55087a8c7 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2020 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.session.room.timeline + +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.extensions.orFalse +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.message.MessageContent +import im.vector.matrix.android.api.session.room.timeline.Timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.CryptoTestHelper +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.junit.Assert.assertTrue +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import timber.log.Timber +import java.util.concurrent.CountDownLatch + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class TimelineBackToPreviousLastForwardTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) + + /** + * This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink of an + * even contained in a previous lastForward chunk, we will be able to go back to the live + */ + @Test + fun backToPreviousLastForwardTest() { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession!! + val aliceRoomId = cryptoTestData.roomId + + aliceSession.cryptoService().setWarnOnUnknownDevices(false) + bobSession.cryptoService().setWarnOnUnknownDevices(false) + + val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! + val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! + + val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) + bobTimeline.start() + + var roomCreationEventId: String? = null + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + roomCreationEventId = snapshot.lastOrNull()?.root?.eventId + // Ok, we have the 8 first messages of the initial sync (room creation and bob join event) + snapshot.size == 8 + } + + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + // Bob stop to sync + bobSession.stopSync() + + // Alice sends 30 messages + commonTestHelper.sendTextMessage( + roomFromAlicePOV, + "First messages from Alice", + 30) + + // Bob start to sync + bobSession.startSync(true) + + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Ok, we have the 10 last messages from Alice. + snapshot.size == 10 + && snapshot.all { it.root.content.toModel()?.body?.startsWith("First messages from Alice").orFalse() } + } + + bobTimeline.addListener(eventsListener) + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + } + + // Bob navigate to the first event (room creation event), so inside the previous last forward chunk + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // The event is in db, so it is fetch and auto pagination occurs, half of the number of events we have for this chunk (?) + snapshot.size == 4 + } + + bobTimeline.addListener(eventsListener) + + // Restart the timeline to the first sent event, which is already in the database, so pagination should start automatically + assertTrue(roomFromBobPOV.getTimeLineEvent(roomCreationEventId!!) != null) + + bobTimeline.restartWithEventId(roomCreationEventId) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + // Bob scroll to the future + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Bob can see the first event of the room (so Back pagination has worked) + snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE + // 8 for room creation item, and 30 for the forward pagination + && snapshot.size == 8 + } + + bobTimeline.addListener(eventsListener) + + bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + // Do it again, now we should have a next token, so we can paginate FORWARD + run { + val lock = CountDownLatch(1) + val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> + Timber.e("Bob timeline updated: with ${snapshot.size} events:") + snapshot.forEach { + Timber.w(" event ${it.root}") + } + + // Bob can see the first event of the room (so Back pagination has worked) + snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE + // 8 for room creation item, and 30 for the forward pagination + && snapshot.size == 8 + 30 + } + + bobTimeline.addListener(eventsListener) + + bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) + + commonTestHelper.await(lock) + bobTimeline.removeAllListeners() + + bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() + bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() + } + + bobTimeline.dispose() + + cryptoTestData.cleanUp(commonTestHelper) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 8cfd498cc8..ba46b10228 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -521,6 +521,12 @@ internal class DefaultTimeline( private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) if (token == null) { + val currentChunk = getLiveChunk() + if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk() == true) { + // We are in the case that next event exists, but we do not know the next token. + // Fetch (again) the last event to get a nextToken + currentChunk.timelineEvents.lastOrNull()?.eventId?.let { fetchEvent(it) } + } updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } return } 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 6161e8e5fb..3f5e5ccf06 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 @@ -231,12 +231,20 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy chunks.forEach { if (it != currentChunk) { if (direction == PaginationDirection.FORWARDS && it.hasBeenALastForwardChunk()) { - Timber.d("Do not merge $it") + // Maybe it was a trick to get a nextToken + if (receivedChunk.events.size == 1) { + Timber.d("Receiving a new nextToken") + it.nextToken = receivedChunk.end + chunksToDelete.add(currentChunk) + } else { + Timber.d("Do not merge $it") + chunksToDelete.add(it) + } } else { Timber.d("Merge $it") currentChunk.merge(roomId, it, direction) + chunksToDelete.add(it) } - chunksToDelete.add(it) } } val shouldUpdateSummary = chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS @@ -253,6 +261,8 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy ) roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent } - RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk) + if (currentChunk.isValid) { + RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk) + } } } From 458e3ee5e8b9b6a56371f0bd373a56000bb119ec Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 15 May 2020 20:18:07 +0200 Subject: [PATCH 21/59] Timeline: fetch next token with the help of getContext when required --- .../internal/session/room/RoomModule.kt | 5 + .../session/room/timeline/DefaultTimeline.kt | 100 +++++++++++------- .../room/timeline/DefaultTimelineService.kt | 4 +- .../timeline/FetchNextTokenAndPaginateTask.kt | 66 ++++++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 18 +--- 5 files changed, 138 insertions(+), 55 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt 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 6b003b5ba2..b0a60480e3 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 @@ -56,8 +56,10 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReportCon import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask +import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask +import im.vector.matrix.android.internal.session.room.timeline.FetchNextTokenAndPaginateTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask @@ -143,6 +145,9 @@ internal abstract class RoomModule { @Binds abstract fun bindPaginationTask(task: DefaultPaginationTask): PaginationTask + @Binds + abstract fun bindFetchNextTokenAndPaginateTask(task: DefaultFetchNextTokenAndPaginateTask): FetchNextTokenAndPaginateTask + @Binds abstract fun bindFetchEditHistoryTask(task: DefaultFetchEditHistoryTask): FetchEditHistoryTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index ba46b10228..76cdf8c485 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.timeline import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.extensions.orFalse 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.api.session.events.model.toModel @@ -71,6 +72,7 @@ internal class DefaultTimeline( private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, + private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask, private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, @@ -519,50 +521,44 @@ internal class DefaultTimeline( * This has to be called on TimelineThread as it accesses realm live results */ private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { - val token = getTokenLive(direction) + val currentChunk = getLiveChunk() + val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken if (token == null) { - val currentChunk = getLiveChunk() - if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk() == true) { + if (direction == Timeline.Direction.FORWARDS && currentChunk?.hasBeenALastForwardChunk().orFalse()) { // We are in the case that next event exists, but we do not know the next token. // Fetch (again) the last event to get a nextToken - currentChunk.timelineEvents.lastOrNull()?.eventId?.let { fetchEvent(it) } - } - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - return - } - val params = PaginationTask.Params(roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit) - - Timber.v("Should fetch $limit items $direction") - cancelableBag += paginationTask - .configureWith(params) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: TokenChunkEventPersistor.Result) { - when (data) { - TokenChunkEventPersistor.Result.SUCCESS -> { - Timber.v("Success fetching $limit items $direction from pagination request") - } - TokenChunkEventPersistor.Result.REACHED_END -> { - postSnapshot() - } - TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> - // Database won't be updated, so we force pagination request - BACKGROUND_HANDLER.post { - executePaginationTask(direction, limit) - } + val lastKnownEventId = nonFilteredEvents.firstOrNull()?.eventId + if (lastKnownEventId == null) { + updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } + } else { + val params = FetchNextTokenAndPaginateTask.Params( + roomId = roomId, + limit = limit, + lastKnownEventId = lastKnownEventId + ) + cancelableBag += fetchNextTokenAndPaginateTask + .configureWith(params) { + this.callback = createPaginationCallback(limit, direction) } - } - - override fun onFailure(failure: Throwable) { - updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } - postSnapshot() - Timber.v("Failure fetching $limit items $direction from pagination request") - } - } + .executeBy(taskExecutor) } - .executeBy(taskExecutor) + } else { + updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } + } + } else { + val params = PaginationTask.Params( + roomId = roomId, + from = token, + direction = direction.toPaginationDirection(), + limit = limit + ) + Timber.v("Should fetch $limit items $direction") + cancelableBag += paginationTask + .configureWith(params) { + this.callback = createPaginationCallback(limit, direction) + } + .executeBy(taskExecutor) + } } // For debug purpose only @@ -743,6 +739,32 @@ internal class DefaultTimeline( forwardsState.set(State()) } + private fun createPaginationCallback(limit: Int, direction: Timeline.Direction): MatrixCallback { + return object : MatrixCallback { + override fun onSuccess(data: TokenChunkEventPersistor.Result) { + when (data) { + TokenChunkEventPersistor.Result.SUCCESS -> { + Timber.v("Success fetching $limit items $direction from pagination request") + } + TokenChunkEventPersistor.Result.REACHED_END -> { + postSnapshot() + } + TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> + // Database won't be updated, so we force pagination request + BACKGROUND_HANDLER.post { + executePaginationTask(direction, limit) + } + } + } + + override fun onFailure(failure: Throwable) { + updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } + postSnapshot() + Timber.v("Failure fetching $limit items $direction from pagination request") + } + } + } + // Extension methods *************************************************************************** private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index c02bb915ef..ffa282d088 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -42,6 +42,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv private val contextOfEventTask: GetContextOfEventTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, + private val fetchNextTokenAndPaginateTask: FetchNextTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper ) : TimelineService { @@ -63,7 +64,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv settings = settings, hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), eventBus = eventBus, - eventDecryptor = eventDecryptor + eventDecryptor = eventDecryptor, + fetchNextTokenAndPaginateTask = fetchNextTokenAndPaginateTask ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt new file mode 100644 index 0000000000..1189e627c4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/FetchNextTokenAndPaginateTask.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 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.timeline + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.query.findIncludingEvent +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.filter.FilterRepository +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitTransaction +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface FetchNextTokenAndPaginateTask : Task { + + data class Params( + val roomId: String, + val lastKnownEventId: String, + val limit: Int + ) +} + +internal class DefaultFetchNextTokenAndPaginateTask @Inject constructor( + private val roomAPI: RoomAPI, + private val monarchy: Monarchy, + private val filterRepository: FilterRepository, + private val paginationTask: PaginationTask, + private val eventBus: EventBus +) : FetchNextTokenAndPaginateTask { + + override suspend fun execute(params: FetchNextTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { + val filter = filterRepository.getRoomFilter() + val response = executeRequest(eventBus) { + apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) + } + if (response.end == null) { + throw IllegalStateException("No next token found") + } + monarchy.awaitTransaction { + ChunkEntity.findIncludingEvent(it, params.lastKnownEventId)?.nextToken = response.end + } + val paginationParams = PaginationTask.Params( + roomId = params.roomId, + from = response.end, + direction = PaginationDirection.FORWARDS, + limit = params.limit + ) + return paginationTask.execute(paginationParams) + } +} 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 3f5e5ccf06..f7411b3bf1 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 @@ -230,21 +230,9 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy val chunksToDelete = ArrayList() chunks.forEach { if (it != currentChunk) { - if (direction == PaginationDirection.FORWARDS && it.hasBeenALastForwardChunk()) { - // Maybe it was a trick to get a nextToken - if (receivedChunk.events.size == 1) { - Timber.d("Receiving a new nextToken") - it.nextToken = receivedChunk.end - chunksToDelete.add(currentChunk) - } else { - Timber.d("Do not merge $it") - chunksToDelete.add(it) - } - } else { - Timber.d("Merge $it") - currentChunk.merge(roomId, it, direction) - chunksToDelete.add(it) - } + Timber.d("Merge $it") + currentChunk.merge(roomId, it, direction) + chunksToDelete.add(it) } } val shouldUpdateSummary = chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS From e0977dd97bbeaff46b4052424194d629bded4ba9 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 13 May 2020 19:31:46 +0200 Subject: [PATCH 22/59] Add new key agreement protocol --- .../internal/crypto/verification/SASTest.kt | 20 ++++++-- ...comingSASDefaultVerificationTransaction.kt | 43 ++++++++++++----- ...tgoingSASDefaultVerificationTransaction.kt | 46 +++++++++++++------ .../DefaultVerificationService.kt | 2 +- .../SASDefaultVerificationTransaction.kt | 6 ++- 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index 9bdd8f1131..460c411d43 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -468,14 +468,19 @@ class SASTest : InstrumentedTest { val aliceSASLatch = CountDownLatch(1) val aliceListener = object : VerificationService.Listener { + var matchOnce = true override fun transactionUpdated(tx: VerificationTransaction) { val uxState = (tx as OutgoingSasVerificationTransaction).uxState + Log.v("TEST", "== aliceState ${uxState.name}") when (uxState) { OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { tx.userHasVerifiedShortCode() } OutgoingSasVerificationTransaction.UxState.VERIFIED -> { - aliceSASLatch.countDown() + if (matchOnce) { + matchOnce = false + aliceSASLatch.countDown() + } } else -> Unit } @@ -485,14 +490,23 @@ class SASTest : InstrumentedTest { val bobSASLatch = CountDownLatch(1) val bobListener = object : VerificationService.Listener { + var acceptOnce = true + var matchOnce = true override fun transactionUpdated(tx: VerificationTransaction) { val uxState = (tx as IncomingSasVerificationTransaction).uxState + Log.v("TEST", "== bobState ${uxState.name}") when (uxState) { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { - tx.performAccept() + if (acceptOnce) { + acceptOnce = false + tx.performAccept() + } } IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { - tx.userHasVerifiedShortCode() + if (matchOnce) { + matchOnce = false + tx.userHasVerifiedShortCode() + } } IncomingSasVerificationTransaction.UxState.VERIFIED -> { bobSASLatch.countDown() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index e3a765f95c..b1368b45b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -198,18 +198,8 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( // using the result as the shared secret. getSAS().setTheirPublicKey(otherKey) - // (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, - // the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of: - // - the string “MATRIX_KEY_VERIFICATION_SAS”, - // - the Matrix ID of the user who sent the m.key.verification.start message, - // - the device ID of the device that sent the m.key.verification.start message, - // - the Matrix ID of the user who sent the m.key.verification.accept message, - // - he device ID of the device that sent the m.key.verification.accept message - // - the transaction ID. - val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId" - // decimal: generate five bytes by using HKDF. - // emoji: generate six bytes by using HKDF. - shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) + + shortCodeBytes = calculateSASBytes() if (BuildConfig.LOG_PRIVATE_DATA) { Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") @@ -219,6 +209,35 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( state = VerificationTxState.ShortCodeReady } + private fun calculateSASBytes(): ByteArray { + when (accepted?.keyAgreementProtocol) { + KEY_AGREEMENT_V1 -> { + // (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, + // the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of: + // - the string “MATRIX_KEY_VERIFICATION_SAS”, + // - the Matrix ID of the user who sent the m.key.verification.start message, + // - the device ID of the device that sent the m.key.verification.start message, + // - the Matrix ID of the user who sent the m.key.verification.accept message, + // - he device ID of the device that sent the m.key.verification.accept message + // - the transaction ID. + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId" + + // decimal: generate five bytes by using HKDF. + // emoji: generate six bytes by using HKDF. + return getSAS().generateShortCode(sasInfo, 6) + } + KEY_AGREEMENT_V2 -> { + // Adds the SAS public key, and separate by | + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$otherUserId|$otherDeviceId|$otherKey|$userId|$deviceId|${getSAS().publicKey}|$transactionId" + return getSAS().generateShortCode(sasInfo, 6) + } + else -> { + // Protocol has been checked earlier + throw IllegalArgumentException() + } + } + } + override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) { Timber.v("## SAS I: received mac for request id:$transactionId") // Check for state? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt index 1480029d6d..8081c07bcc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt @@ -193,18 +193,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( if (accepted!!.commitment.equals(otherCommitment)) { getSAS().setTheirPublicKey(otherKey) - // (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, - // the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of: - // - the string “MATRIX_KEY_VERIFICATION_SAS”, - // - the Matrix ID of the user who sent the m.key.verification.start message, - // - the device ID of the device that sent the m.key.verification.start message, - // - the Matrix ID of the user who sent the m.key.verification.accept message, - // - he device ID of the device that sent the m.key.verification.accept message - // - the transaction ID. - val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId" - // decimal: generate five bytes by using HKDF. - // emoji: generate six bytes by using HKDF. - shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) + shortCodeBytes = calculateSASBytes() state = VerificationTxState.ShortCodeReady } else { // bad commitment @@ -212,14 +201,45 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( } } + private fun calculateSASBytes(): ByteArray { + when (accepted?.keyAgreementProtocol) { + KEY_AGREEMENT_V1 -> { + // (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function, + // the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of: + // - the string “MATRIX_KEY_VERIFICATION_SAS”, + // - the Matrix ID of the user who sent the m.key.verification.start message, + // - the device ID of the device that sent the m.key.verification.start message, + // - the Matrix ID of the user who sent the m.key.verification.accept message, + // - he device ID of the device that sent the m.key.verification.accept message + // - the transaction ID. + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId" + + // decimal: generate five bytes by using HKDF. + // emoji: generate six bytes by using HKDF. + return getSAS().generateShortCode(sasInfo, 6) + } + KEY_AGREEMENT_V2 -> { + // Adds the SAS public key, and separate by | + val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId" + return getSAS().generateShortCode(sasInfo, 6) + } + else -> { + // Protocol has been checked earlier + throw IllegalArgumentException() + } + } + } + override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) { Timber.v("## SAS O: onKeyVerificationMac id:$transactionId") + // There is starting to be a huge amount of state / race here :/ if (state != VerificationTxState.OnKeyReceived && state != VerificationTxState.ShortCodeReady && state != VerificationTxState.ShortCodeAccepted + && state != VerificationTxState.KeySent && state != VerificationTxState.SendingMac && state != VerificationTxState.MacSent) { - Timber.e("## SAS O: received key from invalid state $state") + Timber.e("## SAS O: received mac from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 7479c55aa3..ebe1fad779 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -611,7 +611,7 @@ internal class DefaultVerificationService @Inject constructor( if (validCancelReq == null) { // ignore - Timber.e("## SAS Received invalid key request") + Timber.e("## SAS Received invalid cancelt request") // TODO should we cancel? return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 2a502730fa..7048d790a0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.extensions.toUnsignedInt import im.vector.matrix.android.internal.util.withoutPrefix @@ -66,8 +65,11 @@ internal abstract class SASDefaultVerificationTransaction( const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" const val SAS_MAC_SHA256 = "hkdf-hmac-sha256" + // Deprecated maybe removed later, use V2 + const val KEY_AGREEMENT_V1 = "curve25519" + const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256" // ordered by preferred order - val KNOWN_AGREEMENT_PROTOCOLS = listOf(MXKey.KEY_CURVE_25519_TYPE) + val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1) // ordered by preferred order val KNOWN_HASHES = listOf("sha256") // ordered by preferred order From f2fa57224be2d96f65e1b5a1504eb136697ca6a0 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 19 May 2020 09:55:29 +0200 Subject: [PATCH 23/59] Update matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt Co-authored-by: Hubert Chathi --- .../internal/crypto/verification/DefaultVerificationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index ebe1fad779..0f1666bd9d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -611,7 +611,7 @@ internal class DefaultVerificationService @Inject constructor( if (validCancelReq == null) { // ignore - Timber.e("## SAS Received invalid cancelt request") + Timber.e("## SAS Received invalid cancel request") // TODO should we cancel? return } From 861a7f791feee6ffed7bbadd0c9149a8fa3be6a8 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 19 May 2020 09:57:54 +0200 Subject: [PATCH 24/59] Update change log --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a4767f8be9..409cd532ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,7 @@ Build 🧱: - Other changes: - - + - support new key agreement method for SAS (#1374) Changes in RiotX 0.20.0 (2020-05-15) =================================================== From cad14c93d0f1631a2e2c88c1b3377a35e67649ec Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 19 May 2020 14:39:42 +0200 Subject: [PATCH 25/59] Timeline: fix tests and add message order check --- .../matrix/android/common/CommonTestHelper.kt | 10 +++++ .../TimelineBackToPreviousLastForwardTest.kt | 38 ++++--------------- .../timeline/TimelineForwardPaginationTest.kt | 8 ++-- .../TimelinePreviousLastForwardTest.kt | 13 +++++-- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 2529be9547..9b3ebad03b 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -368,3 +368,13 @@ class CommonTestHelper(context: Context) { session.close() } } + +fun List.checkSendOrder(baseTextMessage: String, numberOfMessages: Int, startIndex: Int): Boolean { + return drop(startIndex) + .take(numberOfMessages) + .foldRightIndexed(true) { index, timelineEvent, acc -> + val body = timelineEvent.root.content.toModel()?.body + val currentMessageSuffix = numberOfMessages - index + acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix")) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt index d55087a8c7..22e9c2f353 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.common.checkSendOrder import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.Assert.assertTrue @@ -90,10 +91,12 @@ class TimelineBackToPreviousLastForwardTest : InstrumentedTest { // Bob stop to sync bobSession.stopSync() + val messageRoot = "First messages from Alice" + // Alice sends 30 messages commonTestHelper.sendTextMessage( roomFromAlicePOV, - "First messages from Alice", + messageRoot, 30) // Bob start to sync @@ -109,7 +112,7 @@ class TimelineBackToPreviousLastForwardTest : InstrumentedTest { // Ok, we have the 10 last messages from Alice. snapshot.size == 10 - && snapshot.all { it.root.content.toModel()?.body?.startsWith("First messages from Alice").orFalse() } + && snapshot.all { it.root.content.toModel()?.body?.startsWith(messageRoot).orFalse() } } bobTimeline.addListener(eventsListener) @@ -159,33 +162,8 @@ class TimelineBackToPreviousLastForwardTest : InstrumentedTest { // Bob can see the first event of the room (so Back pagination has worked) snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE // 8 for room creation item, and 30 for the forward pagination - && snapshot.size == 8 - } - - bobTimeline.addListener(eventsListener) - - bobTimeline.paginate(Timeline.Direction.FORWARDS, 50) - - commonTestHelper.await(lock) - bobTimeline.removeAllListeners() - - bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeTrue() - bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - } - - // Do it again, now we should have a next token, so we can paginate FORWARD - run { - val lock = CountDownLatch(1) - val eventsListener = commonTestHelper.createEventListener(lock) { snapshot -> - Timber.e("Bob timeline updated: with ${snapshot.size} events:") - snapshot.forEach { - Timber.w(" event ${it.root}") - } - - // Bob can see the first event of the room (so Back pagination has worked) - snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE - // 8 for room creation item, and 30 for the forward pagination - && snapshot.size == 8 + 30 + && snapshot.size == 38 + && snapshot.checkSendOrder(messageRoot, 30, 0) } bobTimeline.addListener(eventsListener) @@ -197,8 +175,8 @@ class TimelineBackToPreviousLastForwardTest : InstrumentedTest { bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - } + } bobTimeline.dispose() cryptoTestData.cleanUp(commonTestHelper) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt index ae6a9f8d42..adb5c81378 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineForwardPaginationTest.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.common.checkSendOrder import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder @@ -58,9 +59,10 @@ class TimelineForwardPaginationTest : InstrumentedTest { val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! // Alice sends X messages + val message = "Message from Alice" val sentMessages = commonTestHelper.sendTextMessage( roomFromAlicePOV, - "Message from Alice", + message, numberOfMessagesToSend) // Alice clear the cache @@ -85,7 +87,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { // Ok, we have the 10 last messages of the initial sync snapshot.size == 10 - && snapshot.all { it.root.content.toModel()?.body?.startsWith("Message from Alice").orFalse() } + && snapshot.all { it.root.content.toModel()?.body?.startsWith(message).orFalse() } } // Open the timeline at last sent message @@ -163,9 +165,9 @@ class TimelineForwardPaginationTest : InstrumentedTest { snapshot.forEach { Timber.w(" event ${it.root.content}") } - // 6 for room creation item (backward pagination),and numberOfMessagesToSend (all the message of the room) snapshot.size == 6 + numberOfMessagesToSend + && snapshot.checkSendOrder(message, numberOfMessagesToSend, 0) } aliceTimeline.addListener(aliceEventsListener) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt index 0ca5cfdda0..3e673e4c08 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper +import im.vector.matrix.android.common.checkSendOrder import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder @@ -85,10 +86,11 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Bob stop to sync bobSession.stopSync() + val firstMessage = "First messages from Alice" // Alice sends 30 messages val firstMessageFromAliceId = commonTestHelper.sendTextMessage( roomFromAlicePOV, - "First messages from Alice", + firstMessage, 30) .last() .eventId @@ -106,7 +108,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk snapshot.size == 10 - && snapshot.all { it.root.content.toModel()?.body?.startsWith("First messages from Alice").orFalse() } + && snapshot.all { it.root.content.toModel()?.body?.startsWith(firstMessage).orFalse() } } bobTimeline.addListener(eventsListener) @@ -120,10 +122,11 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Bob stop to sync bobSession.stopSync() + val secondMessage = "Second messages from Alice" // Alice sends again 30 messages commonTestHelper.sendTextMessage( roomFromAlicePOV, - "Second messages from Alice", + secondMessage, 30) // Bob start to sync @@ -139,7 +142,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Ok, we have the 10 last messages from Alice. This will be our future previous lastForward chunk snapshot.size == 10 - && snapshot.all { it.root.content.toModel()?.body?.startsWith("Second messages from Alice").orFalse() } + && snapshot.all { it.root.content.toModel()?.body?.startsWith(secondMessage).orFalse() } } bobTimeline.addListener(eventsListener) @@ -216,6 +219,8 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE // 8 for room creation item 60 message from Alice && snapshot.size == 8 + 60 + && snapshot.checkSendOrder(secondMessage, 30, 0) + && snapshot.checkSendOrder(firstMessage, 30, 30) } bobTimeline.addListener(eventsListener) From 01484978bd3b9392df75cf0035fca06d1a98daa9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 19 May 2020 15:24:36 +0200 Subject: [PATCH 26/59] Fix lint --- .../room/timeline/TimelineBackToPreviousLastForwardTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt index 22e9c2f353..7c7de8170b 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt @@ -175,7 +175,6 @@ class TimelineBackToPreviousLastForwardTest : InstrumentedTest { bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS).shouldBeFalse() bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS).shouldBeFalse() - } bobTimeline.dispose() From d49fcb80fc710b30c157f42c7cd081031538971c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 23:52:41 +0200 Subject: [PATCH 27/59] "Seen by" uses 12h time (Fixes #1378) DateUtils.FORMAT_SHOW_TIME has to be used for i18n to be effective on DateUtils.getRelativeDateTimeString(), do not ask me why. Also internal switching of language does not have effect on this method, you'll have to restart the application. --- CHANGES.md | 1 + .../im/vector/riotx/core/date/VectorDateFormatter.kt | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bb02b798e0..12d4d7a2ca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: Bugfix 🐛: - After jump to unread, newer messages are never loaded (#1008) - Fix issues with FontScale switch (#69, #645) + - "Seen by" uses 12h time (#1378) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/core/date/VectorDateFormatter.kt b/vector/src/main/java/im/vector/riotx/core/date/VectorDateFormatter.kt index 367615d765..344398b91e 100644 --- a/vector/src/main/java/im/vector/riotx/core/date/VectorDateFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/date/VectorDateFormatter.kt @@ -45,11 +45,12 @@ class VectorDateFormatter @Inject constructor(private val context: Context, if (time == null) { return "" } - return DateUtils.getRelativeDateTimeString(context, - time, - DateUtils.DAY_IN_MILLIS, - 2 * DateUtils.DAY_IN_MILLIS, - DateUtils.FORMAT_SHOW_WEEKDAY + return DateUtils.getRelativeDateTimeString( + context, + time, + DateUtils.DAY_IN_MILLIS, + 2 * DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_TIME ).toString() } } From 628439aa6512fa8f63a049e2f97b56402abfa288 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 00:25:59 +0200 Subject: [PATCH 28/59] Mardown: sending "**text in bold** was sending extra paragraph and extra new line --- .../android/internal/extensions/Strings.kt | 24 +++++++++++++++++++ .../room/send/LocalEchoEventFactory.kt | 13 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Strings.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Strings.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Strings.kt new file mode 100644 index 0000000000..09913b9f04 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/extensions/Strings.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 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.extensions + +/** + * Ex: "abcdef".subStringBetween("a", "f") -> "bcde" + * Ex: "abcdefff".subStringBetween("a", "f") -> "bcdeff" + * Ex: "aaabcdef".subStringBetween("a", "f") -> "aabcde" + */ +internal fun String.subStringBetween(prefix: String, suffix: String) = substringAfter(prefix).substringBeforeLast(suffix) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 5f0515e669..9a96e78b24 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -56,6 +56,7 @@ import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.extensions.subStringBetween import im.vector.matrix.android.internal.session.content.ThumbnailExtractor import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils import im.vector.matrix.android.internal.task.TaskExecutor @@ -84,6 +85,7 @@ internal class LocalEchoEventFactory @Inject constructor( ) { // TODO Inject private val parser = Parser.builder().build() + // TODO Inject private val renderer = HtmlRenderer.builder().build() @@ -102,8 +104,15 @@ internal class LocalEchoEventFactory @Inject constructor( val document = parser.parse(source) val htmlText = renderer.render(document) - if (isFormattedTextPertinent(source, htmlText)) { - return TextContent(text.toString(), htmlText) + // Cleanup extra paragraph + val cleanHtmlText = if (htmlText.startsWith("

") && htmlText.endsWith("

\n")) { + htmlText.subStringBetween("

", "

\n") + } else { + htmlText + } + + if (isFormattedTextPertinent(source, cleanHtmlText)) { + return TextContent(text.toString(), cleanHtmlText) } } else { // Try to detect pills From e156a62e19294fd8d7c63722dbbfc284d59e6b5f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 00:27:19 +0200 Subject: [PATCH 29/59] Enable markdown (if active) when sending emote (Fixes #734) --- CHANGES.md | 1 + .../riotx/features/home/room/detail/RoomDetailViewModel.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 12d4d7a2ca..2c7e734600 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Bugfix 🐛: - After jump to unread, newer messages are never loaded (#1008) - Fix issues with FontScale switch (#69, #645) - "Seen by" uses 12h time (#1378) + - Enable markdown (if active) when sending emote (#734) Translations 🗣: - 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 4a767a178e..df6f46b431 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 @@ -117,8 +117,10 @@ class RoomDetailViewModel @AssistedInject constructor( // Slot to keep a pending action during permission request var pendingAction: RoomDetailAction? = null + // Slot to keep a pending uri during permission request var pendingUri: Uri? = null + // Slot to store if we want to prevent preview of attachment var preventAttachmentPreview = false @@ -390,7 +392,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.SendEmote -> { - room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) + room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } From 0e110b0794fae70599df7c921065365f4c9a3629 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 00:39:46 +0200 Subject: [PATCH 30/59] Cleanup: use existing TextContent class instead of Pair<> --- .../ViewEditHistoryEpoxyController.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index 7ac0c8b1e8..329f66459b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply +import im.vector.matrix.android.internal.session.room.send.TextContent import im.vector.riotx.R import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.extensions.localDateTime @@ -90,17 +91,15 @@ class ViewEditHistoryEpoxyController(private val context: Context, } lastDate = evDate val cContent = getCorrectContent(timelineEvent, isOriginalReply) - val body = cContent.second?.let { eventHtmlRenderer.render(it) } - ?: cContent.first + val body = cContent.formattedText?.let { eventHtmlRenderer.render(it) } ?: cContent.text val nextEvent = sourceEvents.getOrNull(index + 1) var spannedDiff: Spannable? = null - if (nextEvent != null && cContent.second == null /*No diff for html*/) { + if (nextEvent != null && cContent.formattedText == null /*No diff for html*/) { // compares the body val nContent = getCorrectContent(nextEvent, isOriginalReply) - val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) } - ?: nContent.first + val nextBody = nContent.formattedText?.let { eventHtmlRenderer.render(it) } ?: nContent.text val dmp = diff_match_patch() val diff = dmp.diff_main(nextBody.toString(), body.toString()) dmp.diff_cleanupSemantic(diff) @@ -138,15 +137,14 @@ class ViewEditHistoryEpoxyController(private val context: Context, } } - private fun getCorrectContent(event: Event, isOriginalReply: Boolean): Pair { + private fun getCorrectContent(event: Event, isOriginalReply: Boolean): TextContent { val clearContent = event.getClearContent().toModel() val newContent = clearContent ?.newContent ?.toModel() if (isOriginalReply) { - return extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: "") to null + return TextContent(extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: "")) } - return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody - ?: clearContent?.formattedBody) + return TextContent(newContent?.body ?: clearContent?.body ?: "", newContent?.formattedBody ?: clearContent?.formattedBody) } } From 7c59bcc928a0ba5fbbfa74f7f518c6eaaef55611 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 00:41:36 +0200 Subject: [PATCH 31/59] Only "org.matrix.custom.html" is supported --- .../api/session/room/model/message/MessageTextContent.kt | 2 +- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt index cc5bb1f774..97923fa99e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt @@ -34,7 +34,7 @@ data class MessageTextContent( @Json(name = "body") override val body: String, /** - * The format used in the formatted_body. Currently only org.matrix.custom.html is supported. + * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported. */ @Json(name = "format") val format: String? = null, 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 ffb71a38c5..9bca721df3 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 @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageAudioConte import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageFormat import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent @@ -350,7 +351,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { - val isFormatted = messageContent.formattedBody.isNullOrBlank().not() + val isFormatted = messageContent.formattedBody.takeIf { messageContent.format == MessageFormat.FORMAT_MATRIX_HTML }.isNullOrBlank().not() return if (isFormatted) { // First detect if the message contains some code block(s) or inline code val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document From b75b29984792a59417418687e9c9d0b6246496c1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 01:05:47 +0200 Subject: [PATCH 32/59] Create MessageContentWithFormattedBody interface --- .../MessageContentWithFormattedBody.kt | 35 +++++++++++++++++++ .../room/model/message/MessageEmoteContent.kt | 8 ++--- .../model/message/MessageNoticeContent.kt | 8 ++--- .../room/model/message/MessageTextContent.kt | 6 ++-- .../room/send/LocalEchoEventFactory.kt | 7 ++-- .../timeline/factory/MessageItemFactory.kt | 3 +- 6 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt new file mode 100644 index 0000000000..cebaf9b8c8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.model.message + +interface MessageContentWithFormattedBody : MessageContent { + /** + * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported. + */ + val format: String? + + /** + * The formatted version of the body. This is required if format is specified. + */ + val formattedBody: String? + + /** + * Get the formattedBody, only if the format is equal to "org.matrix.custom.html" + */ + val matrixFormattedBody: String? + get() = formattedBody?.takeIf { format == MessageFormat.FORMAT_MATRIX_HTML } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt index e7106a9755..7b63959f78 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt @@ -34,15 +34,15 @@ data class MessageEmoteContent( @Json(name = "body") override val body: String, /** - * The format used in the formatted_body. Currently only org.matrix.custom.html is supported. + * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported. */ - @Json(name = "format") val format: String? = null, + @Json(name = "format") override val format: String? = null, /** * The formatted version of the body. This is required if format is specified. */ - @Json(name = "formatted_body") val formattedBody: String? = null, + @Json(name = "formatted_body") override val formattedBody: String? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null -) : MessageContent +) : MessageContentWithFormattedBody diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt index e08e07e9da..41e63bb457 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt @@ -34,15 +34,15 @@ data class MessageNoticeContent( @Json(name = "body") override val body: String, /** - * The format used in the formatted_body. Currently only org.matrix.custom.html is supported. + * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported. */ - @Json(name = "format") val format: String? = null, + @Json(name = "format") override val format: String? = null, /** * The formatted version of the body. This is required if format is specified. */ - @Json(name = "formatted_body") val formattedBody: String? = null, + @Json(name = "formatted_body") override val formattedBody: String? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null -) : MessageContent +) : MessageContentWithFormattedBody diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt index 97923fa99e..d6c54e3ff5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt @@ -36,13 +36,13 @@ data class MessageTextContent( /** * The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported. */ - @Json(name = "format") val format: String? = null, + @Json(name = "format") override val format: String? = null, /** * The formatted version of the body. This is required if format is specified. */ - @Json(name = "formatted_body") val formattedBody: String? = null, + @Json(name = "formatted_body") override val formattedBody: String? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null -) : MessageContent +) : MessageContentWithFormattedBody diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 9a96e78b24..2a24094b5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.room.model.message.FileInfo import im.vector.matrix.android.api.session.room.model.message.ImageInfo import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageContentWithFormattedBody import im.vector.matrix.android.api.session.room.model.message.MessageFileContent import im.vector.matrix.android.api.session.room.model.message.MessageFormat import im.vector.matrix.android.api.session.room.model.message.MessageImageContent @@ -442,10 +443,8 @@ internal class LocalEchoEventFactory @Inject constructor( MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_NOTICE -> { var formattedText: String? = null - if (content is MessageTextContent) { - if (content.format == MessageFormat.FORMAT_MATRIX_HTML) { - formattedText = content.formattedBody - } + if (content is MessageContentWithFormattedBody) { + formattedText = content.matrixFormattedBody } val isReply = content.isReply() || originalContent.isReply() return if (isReply) { 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 9bca721df3..9601327861 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 @@ -31,7 +31,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageAudioConte import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent import im.vector.matrix.android.api.session.room.model.message.MessageFileContent -import im.vector.matrix.android.api.session.room.model.message.MessageFormat import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent import im.vector.matrix.android.api.session.room.model.message.MessageOptionsContent @@ -351,7 +350,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { - val isFormatted = messageContent.formattedBody.takeIf { messageContent.format == MessageFormat.FORMAT_MATRIX_HTML }.isNullOrBlank().not() + val isFormatted = messageContent.matrixFormattedBody.isNullOrBlank().not() return if (isFormatted) { // First detect if the message contains some code block(s) or inline code val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document From 330a33a0e846c3f82d27c1e07fbde60eecb5bb9e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 May 2020 01:47:17 +0200 Subject: [PATCH 33/59] Render formatted_body for m.notice and m.emote (Fixes #1196) --- CHANGES.md | 1 + .../MessageContentWithFormattedBody.kt | 4 +-- .../timeline/factory/MessageItemFactory.kt | 32 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2c7e734600..365ec0c8fc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Features ✨: Improvements 🙌: - Better connectivity lost indicator when airplane mode is on - Add a setting to hide redacted events (#951) + - Render formatted_body for m.notice and m.emote (#1196) Bugfix 🐛: - After jump to unread, newer messages are never loaded (#1008) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt index cebaf9b8c8..b51e3eb841 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContentWithFormattedBody.kt @@ -28,8 +28,8 @@ interface MessageContentWithFormattedBody : MessageContent { val formattedBody: String? /** - * Get the formattedBody, only if the format is equal to "org.matrix.custom.html" + * Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html" */ val matrixFormattedBody: String? - get() = formattedBody?.takeIf { format == MessageFormat.FORMAT_MATRIX_HTML } + get() = formattedBody?.takeIf { it.isNotBlank() && format == MessageFormat.FORMAT_MATRIX_HTML } } 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 9601327861..96abe1ff40 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 @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageContentWithFormattedBody import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent import im.vector.matrix.android.api.session.room.model.message.MessageFileContent import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent @@ -462,14 +463,14 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageTextItem? { - val message = messageContent.body.let { - val formattedBody = span { - text = it - textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) - textStyle = "italic" - } - formattedBody.linkify(callback) + val formattedBody = span { + text = messageContent.getHtmlBody() + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + textStyle = "italic" } + + val message = formattedBody.linkify(callback) + return MessageTextItem_() .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) @@ -483,10 +484,12 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageTextItem? { - val message = messageContent.body.let { - val formattedBody = "* ${informationData.memberName} $it" - formattedBody.linkify(callback) - } + val formattedBody = SpannableStringBuilder() + formattedBody.append("* ${informationData.memberName} ") + formattedBody.append(messageContent.getHtmlBody()) + + val message = formattedBody.linkify(callback) + return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { @@ -502,6 +505,13 @@ class MessageItemFactory @Inject constructor( .movementMethod(createLinkMovementMethod(callback)) } + private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence { + return matrixFormattedBody + ?.let { htmlCompressor.compress(it) } + ?.let { htmlRenderer.get().render(it) } + ?: body + } + private fun buildRedactedItem(attributes: AbsMessageItem.Attributes, highlight: Boolean): RedactedMessageItem? { return RedactedMessageItem_() From fa48e8cffa8aa167a96a679721d143958dd919f0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 18:47:19 +0200 Subject: [PATCH 34/59] Change icon to magnifying-glass to filter room (#1384) --- CHANGES.md | 1 + vector/src/main/res/menu/home.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 365ec0c8fc..d88f9952e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Better connectivity lost indicator when airplane mode is on - Add a setting to hide redacted events (#951) - Render formatted_body for m.notice and m.emote (#1196) + - Change icon to magnifying-glass to filter room (#1384) Bugfix 🐛: - After jump to unread, newer messages are never loaded (#1008) diff --git a/vector/src/main/res/menu/home.xml b/vector/src/main/res/menu/home.xml index 2ef09e0ea1..2fe2464f77 100644 --- a/vector/src/main/res/menu/home.xml +++ b/vector/src/main/res/menu/home.xml @@ -14,7 +14,7 @@ From 691e7fe616c1c7f199b2e9734fe00baa9307a22f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 14:04:13 +0200 Subject: [PATCH 35/59] Kotlin: use orEmpty() --- .../auth/registration/RegistrationFlowResponse.kt | 4 ++-- .../android/internal/crypto/DefaultCryptoService.kt | 4 ++-- .../crypto/actions/EnsureOlmSessionsForUsersAction.kt | 2 +- .../crypto/algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../internal/crypto/algorithms/olm/MXOlmEncryption.kt | 2 +- .../android/internal/crypto/store/db/RealmCryptoStore.kt | 4 ++-- .../android/internal/database/mapper/PushRulesMapper.kt | 2 +- .../android/internal/database/mapper/RoomSummaryMapper.kt | 2 +- .../internal/database/mapper/TimelineEventMapper.kt | 2 +- .../android/internal/session/room/RoomSummaryUpdater.kt | 2 +- .../internal/session/room/draft/DraftRepository.kt | 2 +- .../internal/session/room/read/DefaultReadService.kt | 2 +- .../session/room/relation/UpdateQuickReactionTask.kt | 2 +- .../android/internal/session/terms/TermsResponse.kt | 2 +- .../attachments/preview/AttachmentsPreviewActivity.kt | 2 +- .../features/discovery/DiscoverySettingsViewModel.kt | 8 ++++---- .../im/vector/riotx/features/home/HomeDetailFragment.kt | 4 ++-- .../vector/riotx/features/settings/VectorPreferences.kt | 2 +- .../settings/VectorSettingsSecurityPrivacyFragment.kt | 2 +- 19 files changed, 26 insertions(+), 26 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt index 7512454052..dd5a553193 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/registration/RegistrationFlowResponse.kt @@ -76,7 +76,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult { this.flows?.forEach { it.stages?.mapTo(allFlowTypes) { type -> type } } allFlowTypes.forEach { type -> - val isMandatory = flows?.all { type in it.stages ?: emptyList() } == true + val isMandatory = flows?.all { type in it.stages.orEmpty() } == true val stage = when (type) { LoginFlowTypes.RECAPTCHA -> Stage.ReCaptcha(isMandatory, ((params?.get(type) as? Map<*, *>)?.get("public_key") as? String) @@ -88,7 +88,7 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult { else -> Stage.Other(isMandatory, type, (params?.get(type) as? Map<*, *>)) } - if (type in completedStages ?: emptyList()) { + if (type in completedStages.orEmpty()) { completedStage.add(stage) } else { missingStage.add(stage) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index d529cf4ae5..7badb26d8a 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -262,7 +262,7 @@ internal class DefaultCryptoService @Inject constructor( override fun onSuccess(data: DevicesListResponse) { // Save in local DB - cryptoStore.saveMyDevicesInfo(data.devices ?: emptyList()) + cryptoStore.saveMyDevicesInfo(data.devices.orEmpty()) callback.onSuccess(data) } } @@ -446,7 +446,7 @@ internal class DefaultCryptoService @Inject constructor( } override fun getCryptoDeviceInfo(userId: String): List { - return cryptoStore.getUserDeviceList(userId) ?: emptyList() + return cryptoStore.getUserDeviceList(userId).orEmpty() } override fun getLiveCryptoDeviceInfo(): LiveData> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt index 5766ee9980..1d452f4515 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt @@ -34,7 +34,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o suspend fun handle(users: List): MXUsersDevicesMap { Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") val devicesByUser = users.associateWith { userId -> - val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() + val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty() devices.filter { // Don't bother setting up session to ourself 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 59ffa5f874..4c66f63d6a 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 @@ -103,7 +103,7 @@ internal class MXMegolmDecryption(private val userId: String, senderCurve25519Key = olmDecryptionResult.senderKey, claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"), forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain - ?: emptyList() + .orEmpty() ) } else { throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt index a9b84a8e48..fc40331af2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmEncryption.kt @@ -44,7 +44,7 @@ internal class MXOlmEncryption( ensureSession(userIds) val deviceInfos = ArrayList() for (userId in userIds) { - val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() + val devices = cryptoStore.getUserDevices(userId)?.values.orEmpty() for (device in devices) { val key = device.identityKey() if (key == olmDevice.deviceCurve25519Key) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 7064663995..6af96f886d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -450,7 +450,7 @@ internal class RealmCryptoStore @Inject constructor( } ) return Transformations.map(liveData) { - it.firstOrNull() ?: emptyList() + it.firstOrNull().orEmpty() } } @@ -480,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor( } ) return Transformations.map(liveData) { - it.firstOrNull() ?: emptyList() + it.firstOrNull().orEmpty() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt index a0f644a7cf..4425582a9b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/PushRulesMapper.kt @@ -45,7 +45,7 @@ internal object PushRulesMapper { private fun fromActionStr(actionsStr: String?): List { try { - return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList() + return actionsStr?.let { moshiActionsAdapter.fromJson(it) }.orEmpty() } catch (e: Throwable) { Timber.e(e, "## failed to map push rule actions <$actionsStr>") return emptyList() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 20651069b0..9d3da20b0e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -49,7 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa membership = roomSummaryEntity.membership, versioningState = roomSummaryEntity.versioningState, readMarkerId = roomSummaryEntity.readMarkerId, - userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(), + userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) }.orEmpty(), canonicalAlias = roomSummaryEntity.canonicalAlias, aliases = roomSummaryEntity.aliases.toList(), isEncrypted = roomSummaryEntity.isEncrypted, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 4bd9b9855b..49710e2ade 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -49,7 +49,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS it.user }?.sortedByDescending { it.originServerTs - } ?: emptyList() + }.orEmpty() ) } } 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 6e0adccfb9..d6c1a3ada8 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 @@ -131,7 +131,7 @@ internal class RoomSummaryUpdater @Inject constructor( ?.canonicalAlias val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases - ?: emptyList() + .orEmpty() roomSummaryEntity.aliases.clear() roomSummaryEntity.aliases.addAll(roomAliases) roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/draft/DraftRepository.kt index b00bf2aadb..8740567fc8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/draft/DraftRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/draft/DraftRepository.kt @@ -143,7 +143,7 @@ class DraftRepository @Inject constructor(private val monarchy: Monarchy) { } ) return Transformations.map(liveData) { - it.firstOrNull() ?: emptyList() + it.firstOrNull().orEmpty() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index 59a7dc4b9c..252c8a31fd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -111,7 +111,7 @@ internal class DefaultReadService @AssistedInject constructor( { readReceiptsSummaryMapper.map(it) } ) return Transformations.map(liveRealmData) { - it.firstOrNull() ?: emptyList() + it.firstOrNull().orEmpty() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt index 6ec316e9a4..9684161c4e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt @@ -48,7 +48,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(private val mo monarchy.doWithRealm { realm -> res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId) } - return UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList()) + return UpdateQuickReactionTask.Result(res?.first, res?.second.orEmpty()) } private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String): Pair?> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt index 7c6451e3a0..394ee958ac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/terms/TermsResponse.kt @@ -44,7 +44,7 @@ data class TermsResponse( version = tos[VERSION] as? String ) } - }?.filterNotNull() ?: emptyList() + }?.filterNotNull().orEmpty() } private companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewActivity.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewActivity.kt index 6c91f70131..21febd81a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewActivity.kt @@ -43,7 +43,7 @@ class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { } fun getOutput(intent: Intent): List { - return intent.getParcelableArrayListExtra(ATTACHMENTS_PREVIEW_RESULT) ?: emptyList() + return intent.getParcelableArrayListExtra(ATTACHMENTS_PREVIEW_RESULT).orEmpty() } fun getKeepOriginalSize(intent: Intent): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt index 7c5086afa7..9693b6a4ad 100644 --- a/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/discovery/DiscoverySettingsViewModel.kt @@ -151,8 +151,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor( private fun changeThreePidState(threePid: ThreePid, state: Async) { setState { - val currentMails = emailList() ?: emptyList() - val phones = phoneNumbersList() ?: emptyList() + val currentMails = emailList().orEmpty() + val phones = phoneNumbersList().orEmpty() copy( emailList = Success( currentMails.map { @@ -178,8 +178,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor( private fun changeThreePidSubmitState(threePid: ThreePid, submitState: Async) { setState { - val currentMails = emailList() ?: emptyList() - val phones = phoneNumbersList() ?: emptyList() + val currentMails = emailList().orEmpty() + val phones = phoneNumbersList().orEmpty() copy( emailList = Success( currentMails.map { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index b17fb87f50..4e5d37af6c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -123,12 +123,12 @@ class HomeDetailFragment @Inject constructor( ?.navigator ?.requestSessionVerification(requireContext(), newest.deviceId ?: "") unknownDeviceDetectorSharedViewModel.handle( - UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) } ?: emptyList()) + UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty()) ) } dismissedAction = Runnable { unknownDeviceDetectorSharedViewModel.handle( - UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) } ?: emptyList()) + UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(newest.deviceId?.let { listOf(it) }.orEmpty()) ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 1455e2f8d8..86a9ee678e 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -382,7 +382,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun getUnknownDeviceDismissedList(): List { return tryThis { defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList() - } ?: emptyList() + }.orEmpty() } /** diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 491890de7e..76065b63ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -415,7 +415,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( session.cryptoService().fetchDevicesList(object : MatrixCallback { override fun onSuccess(data: DevicesListResponse) { if (isAdded) { - refreshCryptographyPreference(data.devices ?: emptyList()) + refreshCryptographyPreference(data.devices.orEmpty()) } } From 6e01b75b2f1945b1634075ff1353c938404a43f0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 12:17:25 +0200 Subject: [PATCH 36/59] Dagger: use generic name for parameters --- .../android/internal/session/SessionModule.kt | 20 +++++++++---------- .../internal/session/content/ContentModule.kt | 4 ++-- .../user/accountdata/AccountDataModule.kt | 2 +- .../im/vector/riotx/core/di/VectorModule.kt | 4 ++-- .../features/home/room/list/RoomListModule.kt | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) 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 9d5772b82a..c7afcc1d47 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 @@ -229,40 +229,40 @@ internal abstract class SessionModule { abstract fun bindSession(session: DefaultSession): Session @Binds - abstract fun bindNetworkConnectivityChecker(networkConnectivityChecker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker + abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker @Binds @IntoSet - abstract fun bindGroupSummaryUpdater(groupSummaryUpdater: GroupSummaryUpdater): LiveEntityObserver + abstract fun bindGroupSummaryUpdater(updater: GroupSummaryUpdater): LiveEntityObserver @Binds @IntoSet - abstract fun bindEventsPruner(eventsPruner: EventsPruner): LiveEntityObserver + abstract fun bindEventsPruner(pruner: EventsPruner): LiveEntityObserver @Binds @IntoSet - abstract fun bindEventRelationsAggregationUpdater(eventRelationsAggregationUpdater: EventRelationsAggregationUpdater): LiveEntityObserver + abstract fun bindEventRelationsAggregationUpdater(updater: EventRelationsAggregationUpdater): LiveEntityObserver @Binds @IntoSet - abstract fun bindRoomTombstoneEventLiveObserver(roomTombstoneEventLiveObserver: RoomTombstoneEventLiveObserver): LiveEntityObserver + abstract fun bindRoomTombstoneEventLiveObserver(observer: RoomTombstoneEventLiveObserver): LiveEntityObserver @Binds @IntoSet - abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver + abstract fun bindRoomCreateEventLiveObserver(observer: RoomCreateEventLiveObserver): LiveEntityObserver @Binds @IntoSet - abstract fun bindVerificationMessageLiveObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver + abstract fun bindVerificationMessageLiveObserver(observer: VerificationMessageLiveObserver): LiveEntityObserver @Binds - abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService + abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService @Binds - abstract fun bindSecureStorageService(secureStorageService: DefaultSecureStorageService): SecureStorageService + abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService @Binds - abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService + abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService @Binds abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt index 1faf489dc4..577626c8ac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ContentModule.kt @@ -25,8 +25,8 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver internal abstract class ContentModule { @Binds - abstract fun bindContentUploadStateTracker(contentUploadStateTracker: DefaultContentUploadStateTracker): ContentUploadStateTracker + abstract fun bindContentUploadStateTracker(tracker: DefaultContentUploadStateTracker): ContentUploadStateTracker @Binds - abstract fun bindContentUrlResolver(contentUrlResolver: DefaultContentUrlResolver): ContentUrlResolver + abstract fun bindContentUrlResolver(resolver: DefaultContentUrlResolver): ContentUrlResolver } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt index 1fd4162d0a..2378ab3f0e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt @@ -41,5 +41,5 @@ internal abstract class AccountDataModule { abstract fun bindSaveBreadcrumbsTask(task: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask @Binds - abstract fun bindUpdateBreadcrumsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask + abstract fun bindUpdateBreadcrumbsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask } diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt index 848c1e0d97..3665df31dd 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorModule.kt @@ -75,8 +75,8 @@ abstract class VectorModule { abstract fun bindNavigator(navigator: DefaultNavigator): Navigator @Binds - abstract fun bindErrorFormatter(errorFormatter: DefaultErrorFormatter): ErrorFormatter + abstract fun bindErrorFormatter(formatter: DefaultErrorFormatter): ErrorFormatter @Binds - abstract fun bindUiStateRepository(uiStateRepository: SharedPreferencesUiStateRepository): UiStateRepository + abstract fun bindUiStateRepository(repository: SharedPreferencesUiStateRepository): UiStateRepository } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt index 4541b5d2b5..411db4d679 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListModule.kt @@ -23,5 +23,5 @@ import dagger.Module abstract class RoomListModule { @Binds - abstract fun providesRoomListViewModelFactory(roomListViewModelFactory: RoomListViewModelFactory): RoomListViewModel.Factory + abstract fun providesRoomListViewModelFactory(factory: RoomListViewModelFactory): RoomListViewModel.Factory } From e117fec74f1c716a8dc81cbb6991372667cfda6d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 14:07:25 +0200 Subject: [PATCH 37/59] Kotlin: use orEmpty() for Maps --- .../matrix/android/internal/crypto/OneTimeKeysUploader.kt | 2 +- .../matrix/android/internal/crypto/model/CryptoInfoMapper.kt | 2 +- .../crypto/store/db/model/OutgoingGossipingRequestEntity.kt | 4 ++-- .../internal/session/identity/todelete/AccountDataMapper.kt | 2 +- .../session/user/accountdata/DefaultAccountDataService.kt | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt index a0483335e5..89965e7da9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt @@ -137,7 +137,7 @@ internal class OneTimeKeysUploader @Inject constructor( private suspend fun uploadOneTimeKeys(oneTimeKeys: Map>?): KeysUploadResponse { val oneTimeJson = mutableMapOf() - val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY) ?: emptyMap() + val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty() curve25519Map.forEach { (key_id, value) -> val k = mutableMapOf() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoInfoMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoInfoMapper.kt index f3ddfb8faa..37aae2c47b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoInfoMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoInfoMapper.kt @@ -47,7 +47,7 @@ internal object CryptoInfoMapper { return CryptoCrossSigningKey( userId = keyInfo.userId, usages = keyInfo.usages, - keys = keyInfo.keys ?: emptyMap(), + keys = keyInfo.keys.orEmpty(), signatures = keyInfo.signatures, trustLevel = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt index 19049c099c..21960ec9a0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -78,7 +78,7 @@ internal open class OutgoingGossipingRequestEntity( GossipRequestType.KEY -> { OutgoingRoomKeyRequest( requestBody = getRequestedKeyInfo(), - recipients = getRecipients() ?: emptyMap(), + recipients = getRecipients().orEmpty(), requestId = requestId ?: "", state = requestState ) @@ -86,7 +86,7 @@ internal open class OutgoingGossipingRequestEntity( GossipRequestType.SECRET -> { OutgoingSecretRequest( secretName = getRequestedSecretName(), - recipients = getRecipients() ?: emptyMap(), + recipients = getRecipients().orEmpty(), requestId = requestId ?: "", state = requestState ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt index 4627911b72..3a736681e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/todelete/AccountDataMapper.kt @@ -30,7 +30,7 @@ internal class AccountDataMapper @Inject constructor(moshi: Moshi) { fun map(entity: UserAccountDataEntity): UserAccountDataEvent { return UserAccountDataEvent( type = entity.type ?: "", - content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() + content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt index 7756b22510..31abc800c6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -67,7 +67,7 @@ internal class DefaultAccountDataService @Inject constructor( entity.type?.let { type -> UserAccountDataEvent( type = type, - content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() + content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) } } @@ -84,7 +84,7 @@ internal class DefaultAccountDataService @Inject constructor( }, { entity -> UserAccountDataEvent( type = entity.type ?: "", - content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() + content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) }) } From 53c7ea2831d32e6b8817c741fa124b0a7a214522 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 15:21:31 +0200 Subject: [PATCH 38/59] Kotlin: use orEmpty() for Maps --- CHANGES.md | 1 + .../vector/riotx/core/extensions/Fragment.kt | 7 ++ .../riotx/features/rageshake/BugReporter.kt | 76 +++++++++++++------ 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d88f9952e4..aadd03d1f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Bugfix 🐛: - Fix issues with FontScale switch (#69, #645) - "Seen by" uses 12h time (#1378) - Enable markdown (if active) when sending emote (#734) + - Screenshots for Rageshake now includes Dialogs such as BottomSheet (#1349) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt index b93ab3fdce..c28dcf12d3 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt @@ -80,5 +80,12 @@ fun VectorBaseFragment.addChildFragmentToBackstack(frameId: Int, } } +/** + * Return a list of all child Fragments, recursively + */ +fun Fragment.getAllChildFragments(): List { + return listOf(this) + childFragmentManager.fragments.map { it.getAllChildFragments() }.flatten() +} + // Define a missing constant const val POP_BACK_STACK_EXCLUSIVE = 0 diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt index a001567635..d9020afab4 100755 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt @@ -19,17 +19,20 @@ package im.vector.riotx.features.rageshake import android.annotation.SuppressLint -import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.Canvas import android.os.AsyncTask import android.os.Build import android.view.View +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentActivity import im.vector.matrix.android.api.Matrix import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.extensions.getAllChildFragments import im.vector.riotx.core.extensions.toOnOff import im.vector.riotx.features.settings.VectorLocale import im.vector.riotx.features.settings.VectorPreferences @@ -423,7 +426,7 @@ class BugReporter @Inject constructor( /** * Send a bug report either with email or with Vector. */ - fun openBugReportScreen(activity: Activity, forSuggestion: Boolean = false) { + fun openBugReportScreen(activity: FragmentActivity, forSuggestion: Boolean = false) { screenshot = takeScreenshot(activity) val intent = Intent(activity, BugReportActivity::class.java) @@ -512,41 +515,64 @@ class BugReporter @Inject constructor( * * @return the screenshot */ - private fun takeScreenshot(activity: Activity): Bitmap? { - // get content view - val contentView = activity.findViewById(android.R.id.content) - if (contentView == null) { - Timber.e("Cannot find content view on $activity. Cannot take screenshot.") - return null - } - + private fun takeScreenshot(activity: FragmentActivity): Bitmap? { // get the root view to snapshot - val rootView = contentView.rootView + val rootView = activity.window?.decorView?.rootView if (rootView == null) { Timber.e("Cannot find root view on $activity. Cannot take screenshot.") return null } - // refresh it + + val mainBitmap = getBitmap(rootView) + + if (mainBitmap == null) { + Timber.e("Cannot get main screenshot") + return null + } + + try { + val cumulBitmap = Bitmap.createBitmap(mainBitmap.width, mainBitmap.height, Bitmap.Config.ARGB_8888) + + val canvas = Canvas(cumulBitmap) + canvas.drawBitmap(mainBitmap, 0f, 0f, null) + // Add the dialogs if any + getDialogBitmaps(activity).forEach { + canvas.drawBitmap(it, 0f, 0f, null) + } + + return cumulBitmap + } catch (e: Throwable) { + Timber.e(e, "Cannot get snapshot of screen: $e") + } + + return null + } + + private fun getDialogBitmaps(activity: FragmentActivity): List { + return activity.supportFragmentManager.fragments + .map { it.getAllChildFragments() } + .flatten() + .filterIsInstance(DialogFragment::class.java) + .mapNotNull { fragment -> + fragment.dialog?.window?.decorView?.rootView?.let { rootView -> + getBitmap(rootView) + } + } + } + + private fun getBitmap(rootView: View): Bitmap? { @Suppress("DEPRECATION") rootView.isDrawingCacheEnabled = false @Suppress("DEPRECATION") rootView.isDrawingCacheEnabled = true - try { + return try { @Suppress("DEPRECATION") - var bitmap = rootView.drawingCache - - // Make a copy, because if Activity is destroyed, the bitmap will be recycled - bitmap = Bitmap.createBitmap(bitmap) - - return bitmap - } catch (oom: OutOfMemoryError) { - Timber.e(oom, "Cannot get drawing cache for $activity OOM.") - } catch (e: Exception) { - Timber.e(e, "Cannot get snapshot of screen: $e") + rootView.drawingCache + } catch (e: Throwable) { + Timber.e(e, "Cannot get snapshot of dialog: $e") + null } - - return null } // ============================================================================================================== From a036be6436d620157a9b5fcbe46d438d063ff5d7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 15:42:58 +0200 Subject: [PATCH 39/59] Fix missing title in BugReport screen --- .../im/vector/riotx/features/rageshake/BugReportActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt index 5c1428cb54..f943186e2e 100755 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt @@ -70,6 +70,8 @@ class BugReportActivity : VectorBaseActivity() { bug_report_button_include_crash_logs.isVisible = false // Keep the screenshot + } else { + supportActionBar?.setTitle(R.string.title_activity_bug_report) } } From 6e57b06673adaac2ade9b41cbb37b4777a9efbf9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 18 May 2020 22:38:07 +0200 Subject: [PATCH 40/59] Ensure Filter model match the spec and add Javadoc --- .../database/query/FilterEntityQueries.kt | 2 +- .../session/filter/DefaultFilterRepository.kt | 20 +++---- .../session/filter/DefaultSaveFilterTask.kt | 4 +- .../internal/session/filter/EventFilter.kt | 59 +++++++++++++++++++ .../android/internal/session/filter/Filter.kt | 48 +++++++++------ .../internal/session/filter/FilterApi.kt | 5 +- .../internal/session/filter/FilterBody.kt | 39 ------------ .../internal/session/filter/FilterFactory.kt | 8 +-- .../session/filter/FilterRepository.kt | 4 +- .../internal/session/filter/FilterResponse.kt | 5 ++ .../internal/session/filter/FilterUtil.kt | 20 +++---- .../session/filter/RoomEventFilter.kt | 32 ++++++++++ .../internal/session/filter/RoomFilter.kt | 25 ++++++++ 13 files changed, 183 insertions(+), 88 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/EventFilter.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterEntityQueries.kt index 6902d39a82..42a84113ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/FilterEntityQueries.kt @@ -35,7 +35,7 @@ internal fun FilterEntity.Companion.get(realm: Realm): FilterEntity? { internal fun FilterEntity.Companion.getOrCreate(realm: Realm): FilterEntity { return get(realm) ?: realm.createObject() .apply { - filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString() + filterBodyJson = FilterFactory.createDefaultFilter().toJSONString() roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString() filterId = "" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt index f7df8c512e..95291de4b6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultFilterRepository.kt @@ -28,25 +28,25 @@ import javax.inject.Inject internal class DefaultFilterRepository @Inject constructor(private val monarchy: Monarchy) : FilterRepository { - override suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean { + override suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - val filter = FilterEntity.get(realm) + val filterEntity = FilterEntity.get(realm) // Filter has changed, or no filter Id yet - filter == null - || filter.filterBodyJson != filterBody.toJSONString() - || filter.filterId.isBlank() + filterEntity == null + || filterEntity.filterBodyJson != filter.toJSONString() + || filterEntity.filterId.isBlank() }.also { hasChanged -> if (hasChanged) { // Filter is new or has changed, store it and reset the filter Id. // This has to be done outside of the Realm.use(), because awaitTransaction change the current thread monarchy.awaitTransaction { realm -> // We manage only one filter for now - val filterBodyJson = filterBody.toJSONString() + val filterJson = filter.toJSONString() val roomEventFilterJson = roomEventFilter.toJSONString() val filterEntity = FilterEntity.getOrCreate(realm) - filterEntity.filterBodyJson = filterBodyJson + filterEntity.filterBodyJson = filterJson filterEntity.roomEventFilterJson = roomEventFilterJson // Reset filterId filterEntity.filterId = "" @@ -55,14 +55,14 @@ internal class DefaultFilterRepository @Inject constructor(private val monarchy: } } - override suspend fun storeFilterId(filterBody: FilterBody, filterId: String) { + override suspend fun storeFilterId(filter: Filter, filterId: String) { monarchy.awaitTransaction { // We manage only one filter for now - val filterBodyJson = filterBody.toJSONString() + val filterJson = filter.toJSONString() // Update the filter id, only if the filter body matches it.where() - .equalTo(FilterEntityFields.FILTER_BODY_JSON, filterBodyJson) + .equalTo(FilterEntityFields.FILTER_BODY_JSON, filterJson) ?.findFirst() ?.filterId = filterId } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt index 47c5e4a08a..f396e01e86 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/DefaultSaveFilterTask.kt @@ -43,10 +43,10 @@ internal class DefaultSaveFilterTask @Inject constructor( override suspend fun execute(params: SaveFilterTask.Params) { val filterBody = when (params.filterPreset) { FilterService.FilterPreset.RiotFilter -> { - FilterFactory.createRiotFilterBody() + FilterFactory.createRiotFilter() } FilterService.FilterPreset.NoFilter -> { - FilterFactory.createDefaultFilterBody() + FilterFactory.createDefaultFilter() } } val roomFilter = when (params.filterPreset) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/EventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/EventFilter.kt new file mode 100644 index 0000000000..f5d159588b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/EventFilter.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.session.filter + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Represents "Filter" as mentioned in the SPEC + * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter + */ +@JsonClass(generateAdapter = true) +data class EventFilter( + /** + * The maximum number of events to return. + */ + @Json(name = "limit") val limit: Int? = null, + /** + * A list of senders IDs to include. If this list is absent then all senders are included. + */ + @Json(name = "senders") val senders: List? = null, + /** + * A list of sender IDs to exclude. If this list is absent then no senders are excluded. + * A matching sender will be excluded even if it is listed in the 'senders' filter. + */ + @Json(name = "not_senders") val notSenders: List? = null, + /** + * A list of event types to include. If this list is absent then all event types are included. + * A '*' can be used as a wildcard to match any sequence of characters. + */ + @Json(name = "types") val types: List? = null, + /** + * A list of event types to exclude. If this list is absent then no event types are excluded. + * A matching type will be excluded even if it is listed in the 'types' filter. + * A '*' can be used as a wildcard to match any sequence of characters. + */ + @Json(name = "not_types") val notTypes: List? = null +) { + fun hasData(): Boolean { + return limit != null + || senders != null + || notSenders != null + || types != null + || notTypes != null + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt index fc0472e32f..4b826a00f8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/Filter.kt @@ -17,28 +17,42 @@ package im.vector.matrix.android.internal.session.filter import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.di.MoshiProvider /** - * Represents "Filter" as mentioned in the SPEC + * Class which can be parsed to a filter json string. Used for POST and GET + * Have a look here for further information: * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter */ @JsonClass(generateAdapter = true) -data class Filter( - @Json(name = "limit") val limit: Int? = null, - @Json(name = "senders") val senders: List? = null, - @Json(name = "not_senders") val notSenders: List? = null, - @Json(name = "types") val types: List? = null, - @Json(name = "not_types") val notTypes: List? = null, - @Json(name = "rooms") val rooms: List? = null, - @Json(name = "not_rooms") val notRooms: List? = null +internal data class Filter( + /** + * List of event fields to include. If this list is absent then all fields are included. The entries may + * include '.' characters to indicate sub-fields. So ['content.body'] will include the 'body' field of the + * 'content' object. A literal '.' character in a field name may be escaped using a '\'. A server may + * include more fields than were requested. + */ + @Json(name = "event_fields") val eventFields: List? = null, + /** + * The format to use for events. 'client' will return the events in a format suitable for clients. + * 'federation' will return the raw event as received over federation. The default is 'client'. One of: ["client", "federation"] + */ + @Json(name = "event_format") val eventFormat: String? = null, + /** + * The presence updates to include. + */ + @Json(name = "presence") val presence: EventFilter? = null, + /** + * The user account data that isn't associated with rooms to include. + */ + @Json(name = "account_data") val accountData: EventFilter? = null, + /** + * Filters to be applied to room data. + */ + @Json(name = "room") val room: RoomFilter? = null ) { - fun hasData(): Boolean { - return (limit != null - || senders != null - || notSenders != null - || types != null - || notTypes != null - || rooms != null - || notRooms != null) + + fun toJSONString(): String { + return MoshiProvider.providesMoshi().adapter(Filter::class.java).toJson(this) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt index 092038ee5d..deae2d5b3a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterApi.kt @@ -32,7 +32,7 @@ internal interface FilterApi { * @param body the Json representation of a FilterBody object */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter") - fun uploadFilter(@Path("userId") userId: String, @Body body: FilterBody): Call + fun uploadFilter(@Path("userId") userId: String, @Body body: Filter): Call /** * Gets a filter with a given filterId from the homeserver @@ -42,6 +42,5 @@ internal interface FilterApi { * @return Filter */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}") - fun getFilterById(@Path("userId") userId: String, @Path("filterId") - filterId: String): Call + fun getFilterById(@Path("userId") userId: String, @Path("filterId") filterId: String): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt deleted file mode 100644 index 535c66f637..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterBody.kt +++ /dev/null @@ -1,39 +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.filter - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import im.vector.matrix.android.internal.di.MoshiProvider - -/** - * Class which can be parsed to a filter json string. Used for POST and GET - * Have a look here for further information: - * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter - */ -@JsonClass(generateAdapter = true) -internal data class FilterBody( - @Json(name = "event_fields") val eventFields: List? = null, - @Json(name = "event_format") val eventFormat: String? = null, - @Json(name = "presence") val presence: Filter? = null, - @Json(name = "account_data") val accountData: Filter? = null, - @Json(name = "room") val room: RoomFilter? = null -) { - - fun toJSONString(): String { - return MoshiProvider.providesMoshi().adapter(FilterBody::class.java).toJson(this) - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt index a070759de9..15fa2f3c31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt @@ -20,12 +20,12 @@ import im.vector.matrix.android.api.session.events.model.EventType internal object FilterFactory { - fun createDefaultFilterBody(): FilterBody { - return FilterUtil.enableLazyLoading(FilterBody(), true) + fun createDefaultFilter(): Filter { + return FilterUtil.enableLazyLoading(Filter(), true) } - fun createRiotFilterBody(): FilterBody { - return FilterBody( + fun createRiotFilter(): Filter { + return Filter( room = RoomFilter( timeline = createRiotTimelineFilter(), state = createRiotStateFilter() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterRepository.kt index d205ea8a87..c558732f44 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterRepository.kt @@ -21,12 +21,12 @@ internal interface FilterRepository { /** * Return true if the filterBody has changed, or need to be sent to the server */ - suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean + suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean /** * Set the filterId of this filter */ - suspend fun storeFilterId(filterBody: FilterBody, filterId: String) + suspend fun storeFilterId(filter: Filter, filterId: String) /** * Return filter json or filter id diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt index 75e2c23da9..a9bfb70d5e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterResponse.kt @@ -24,5 +24,10 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class FilterResponse( + /** + * Required. The ID of the filter that was created. Cannot start with a { as this character + * is used to determine if the filter provided is inline JSON or a previously declared + * filter by homeservers on some APIs. + */ @Json(name = "filter_id") val filterId: String ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt index 3f4e61e6b5..53ede5ad45 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterUtil.kt @@ -81,30 +81,30 @@ internal object FilterUtil { } */ /** - * Compute a new filterBody to enable or disable the lazy loading + * Compute a new filter to enable or disable the lazy loading * * - * If lazy loading is on, the filterBody will looks like + * If lazy loading is on, the filter will looks like * {"room":{"state":{"lazy_load_members":true})} * - * @param filterBody filterBody to patch + * @param filter filter to patch * @param useLazyLoading true to enable lazy loading */ - fun enableLazyLoading(filterBody: FilterBody, useLazyLoading: Boolean): FilterBody { + fun enableLazyLoading(filter: Filter, useLazyLoading: Boolean): Filter { if (useLazyLoading) { // Enable lazy loading - return filterBody.copy( - room = filterBody.room?.copy( - state = filterBody.room.state?.copy(lazyLoadMembers = true) + return filter.copy( + room = filter.room?.copy( + state = filter.room.state?.copy(lazyLoadMembers = true) ?: RoomEventFilter(lazyLoadMembers = true) ) ?: RoomFilter(state = RoomEventFilter(lazyLoadMembers = true)) ) } else { - val newRoomEventFilter = filterBody.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() } - val newRoomFilter = filterBody.room?.copy(state = newRoomEventFilter)?.takeIf { it.hasData() } + val newRoomEventFilter = filter.room?.state?.copy(lazyLoadMembers = null)?.takeIf { it.hasData() } + val newRoomFilter = filter.room?.copy(state = newRoomEventFilter)?.takeIf { it.hasData() } - return filterBody.copy( + return filter.copy( room = newRoomFilter ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt index 9cdccc5c8b..6f2de03b90 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt @@ -25,14 +25,46 @@ import im.vector.matrix.android.internal.di.MoshiProvider */ @JsonClass(generateAdapter = true) data class RoomEventFilter( + /** + * The maximum number of events to return. + */ @Json(name = "limit") var limit: Int? = null, + /** + * A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will + * be excluded even if it is listed in the 'senders' filter. + */ @Json(name = "not_senders") val notSenders: List? = null, + /** + * A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will + * be excluded even if it is listed in the 'types' filter. A '*' can be used as a wildcard to match any sequence of characters. + */ @Json(name = "not_types") val notTypes: List? = null, + /** + * A list of senders IDs to include. If this list is absent then all senders are included. + */ @Json(name = "senders") val senders: List? = null, + /** + * A list of event types to include. If this list is absent then all event types are included. A '*' can be used as + * a wildcard to match any sequence of characters. + */ @Json(name = "types") val types: List? = null, + /** + * A list of room IDs to include. If this list is absent then all rooms are included. + */ @Json(name = "rooms") val rooms: List? = null, + /** + * A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded + * even if it is listed in the 'rooms' filter. + */ @Json(name = "not_rooms") val notRooms: List? = null, + /** + * If true, includes only events with a url key in their content. If false, excludes those events. If omitted, url + * key is not considered for filtering. + */ @Json(name = "contains_url") val containsUrl: Boolean? = null, + /** + * If true, enables lazy-loading of membership events. See Lazy-loading room members for more information. Defaults to false. + */ @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt index 3109763570..e79a0a624e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomFilter.kt @@ -24,12 +24,37 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class RoomFilter( + /** + * A list of room IDs to exclude. If this list is absent then no rooms are excluded. + * A matching room will be excluded even if it is listed in the 'rooms' filter. + * This filter is applied before the filters in ephemeral, state, timeline or account_data + */ @Json(name = "not_rooms") val notRooms: List? = null, + /** + * A list of room IDs to include. If this list is absent then all rooms are included. + * This filter is applied before the filters in ephemeral, state, timeline or account_data + */ @Json(name = "rooms") val rooms: List? = null, + /** + * The events that aren't recorded in the room history, e.g. typing and receipts, to include for rooms. + */ @Json(name = "ephemeral") val ephemeral: RoomEventFilter? = null, + /** + * Include rooms that the user has left in the sync, default false + */ @Json(name = "include_leave") val includeLeave: Boolean? = null, + /** + * The state events to include for rooms. + * Developer remark: StateFilter is exactly the same than RoomEventFilter + */ @Json(name = "state") val state: RoomEventFilter? = null, + /** + * The message and state update events to include for rooms. + */ @Json(name = "timeline") val timeline: RoomEventFilter? = null, + /** + * The per user account data to include for rooms. + */ @Json(name = "account_data") val accountData: RoomEventFilter? = null ) { From d2598480c823d1bdd518a90c71b35a92728534fc Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 18 May 2020 22:43:40 +0200 Subject: [PATCH 41/59] var -> val --- .../matrix/android/internal/session/filter/RoomEventFilter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt index 6f2de03b90..81e7b672b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/RoomEventFilter.kt @@ -28,7 +28,7 @@ data class RoomEventFilter( /** * The maximum number of events to return. */ - @Json(name = "limit") var limit: Int? = null, + @Json(name = "limit") val limit: Int? = null, /** * A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will * be excluded even if it is listed in the 'senders' filter. From 8a9498bae45c1361e2a98c8d80f7e53f975c9090 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 18 May 2020 23:34:42 +0200 Subject: [PATCH 42/59] Uploads: add the service and the task --- .../matrix/android/api/session/room/Room.kt | 2 + .../session/room/uploads/GetUploadsResult.kt | 26 +++++++++ .../session/room/uploads/UploadsService.kt | 35 +++++++++++ .../internal/session/filter/FilterFactory.kt | 9 +++ .../internal/session/room/DefaultRoom.kt | 3 + .../internal/session/room/RoomFactory.kt | 3 + .../internal/session/room/RoomModule.kt | 5 ++ .../room/uploads/DefaultUploadsService.kt | 44 ++++++++++++++ .../session/room/uploads/GetUploadsTask.kt | 58 +++++++++++++++++++ 9 files changed, 185 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadsService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 0c3316e802..2fd7d84f04 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.typing.TypingService +import im.vector.matrix.android.api.session.room.uploads.UploadsService import im.vector.matrix.android.api.util.Optional /** @@ -42,6 +43,7 @@ interface Room : TypingService, MembershipService, StateService, + UploadsService, ReportingService, RelationService, RoomCryptoService, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt new file mode 100644 index 0000000000..b1e70932af --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.uploads + +import im.vector.matrix.android.api.session.events.model.Event + +data class GetUploadsResult( + // List of fetched Events + val events: List, + // token to get more events, or null if there is no more event to fetch + val nextToken: String? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadsService.kt new file mode 100644 index 0000000000..54a87cdcd9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadsService.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.uploads + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.util.Cancelable + +/** + * This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level. + */ +interface UploadsService { + + /** + * Get a list of events containing URL sent to a room, from most recent to oldest one + * @param numberOfEvents the expected number of events to retrieve. The result can contain less events. + * @param since token to get next page, or null to get the first page + */ + fun getUploads(numberOfEvents: Int, + since: String?, + callback: MatrixCallback): Cancelable +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt index 15fa2f3c31..15c57ab1c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/filter/FilterFactory.kt @@ -20,6 +20,15 @@ import im.vector.matrix.android.api.session.events.model.EventType internal object FilterFactory { + fun createUploadsFilter(numberOfEvents: Int): RoomEventFilter { + return RoomEventFilter( + limit = numberOfEvents, + containsUrl = true, + types = listOf(EventType.MESSAGE), + lazyLoadMembers = true + ) + } + fun createDefaultFilter(): Filter { return FilterUtil.enableLazyLoading(Filter(), true) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 833469909f..40d0500a48 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.typing.TypingService +import im.vector.matrix.android.api.session.room.uploads.UploadsService import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -54,6 +55,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val sendService: SendService, private val draftService: DraftService, private val stateService: StateService, + private val uploadsService: UploadsService, private val reportingService: ReportingService, private val readService: ReadService, private val typingService: TypingService, @@ -68,6 +70,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, SendService by sendService, DraftService by draftService, StateService by stateService, + UploadsService by uploadsService, ReportingService by reportingService, ReadService by readService, TypingService by typingService, 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 7c544d64cf..974c30dba9 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 @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService +import im.vector.matrix.android.internal.session.room.uploads.DefaultUploadsService import im.vector.matrix.android.internal.task.TaskExecutor import javax.inject.Inject @@ -47,6 +48,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona private val sendServiceFactory: DefaultSendService.Factory, private val draftServiceFactory: DefaultDraftService.Factory, private val stateServiceFactory: DefaultStateService.Factory, + private val uploadsServiceFactory: DefaultUploadsService.Factory, private val reportingServiceFactory: DefaultReportingService.Factory, private val readServiceFactory: DefaultReadService.Factory, private val typingServiceFactory: DefaultTypingService.Factory, @@ -66,6 +68,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona sendService = sendServiceFactory.create(roomId), draftService = draftServiceFactory.create(roomId), stateService = stateServiceFactory.create(roomId), + uploadsService = uploadsServiceFactory.create(roomId), reportingService = reportingServiceFactory.create(roomId), readService = readServiceFactory.create(roomId), typingService = typingServiceFactory.create(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 b0a60480e3..001ce120c8 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 @@ -64,6 +64,8 @@ import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEvent import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask import im.vector.matrix.android.internal.session.room.typing.SendTypingTask +import im.vector.matrix.android.internal.session.room.uploads.DefaultGetUploadsTask +import im.vector.matrix.android.internal.session.room.uploads.GetUploadsTask import retrofit2.Retrofit @Module @@ -156,4 +158,7 @@ internal abstract class RoomModule { @Binds abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask + + @Binds + abstract fun bindGetUploadsTask(task: DefaultGetUploadsTask): GetUploadsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt new file mode 100644 index 0000000000..e54a3b93fe --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 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.uploads + +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.room.uploads.GetUploadsResult +import im.vector.matrix.android.api.session.room.uploads.UploadsService +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith + +internal class DefaultUploadsService @AssistedInject constructor( + @Assisted private val roomId: String, + private val taskExecutor: TaskExecutor, + private val getUploadsTask: GetUploadsTask +) : UploadsService { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String): UploadsService + } + + override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback): Cancelable { + return getUploadsTask + .configureWith(GetUploadsTask.Params(roomId, numberOfEvents, since)) + .executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt new file mode 100644 index 0000000000..4bed591315 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 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.uploads + +import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.filter.FilterFactory +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection +import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse +import im.vector.matrix.android.internal.session.sync.SyncTokenStore +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface GetUploadsTask : Task { + + data class Params( + val roomId: String, + val numberOfEvents: Int, + val since: String? + ) +} + +internal class DefaultGetUploadsTask @Inject constructor( + private val roomAPI: RoomAPI, + private val tokenStore: SyncTokenStore, + private val eventBus: EventBus) + : GetUploadsTask { + + override suspend fun execute(params: GetUploadsTask.Params): GetUploadsResult { + val since = params.since ?: tokenStore.getLastToken() ?: throw IllegalStateException("No token available") + + val filter = FilterFactory.createUploadsFilter(params.numberOfEvents).toJSONString() + val chunk = executeRequest(eventBus) { + apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) + } + + return GetUploadsResult( + events = chunk.events, // reverse? + nextToken = chunk.end?.takeIf { it != chunk.start } + ) + } +} From 0992e768000a5cd992b0ff068b6c18e7800cdb14 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2020 01:39:04 +0200 Subject: [PATCH 43/59] Uploads: add screen - WIP --- vector/build.gradle | 1 + .../im/vector/riotx/core/di/FragmentModule.kt | 20 ++- .../roomprofile/RoomProfileActivity.kt | 3 +- .../roomprofile/uploads/RoomUploadsAction.kt | 21 ++++ .../uploads/RoomUploadsFragment.kt | 75 +++++++++++ .../uploads/RoomUploadsPagerAdapter.kt | 59 +++++++++ .../uploads/RoomUploadsViewModel.kt | 118 ++++++++++++++++++ .../uploads/RoomUploadsViewState.kt | 40 ++++++ .../uploads/child/RoomUploadsFilesFragment.kt | 33 +++++ .../uploads/child/RoomUploadsMediaFragment.kt | 33 +++++ .../main/res/layout/fragment_room_uploads.xml | 85 +++++++++++++ vector/src/main/res/values/strings.xml | 5 + 12 files changed, 491 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt create mode 100644 vector/src/main/res/layout/fragment_room_uploads.xml diff --git a/vector/build.gradle b/vector/build.gradle index 74fc96a425..b9a7fbcdc8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -282,6 +282,7 @@ dependencies { implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" // Log diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index bc526f93a5..b7b0a1c2f4 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -76,6 +76,9 @@ import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionF import im.vector.riotx.features.roomprofile.RoomProfileFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment +import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment +import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment import im.vector.riotx.features.settings.VectorSettingsLabsFragment @@ -96,9 +99,9 @@ import im.vector.riotx.features.settings.locale.LocalePickerFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment import im.vector.riotx.features.share.IncomingShareFragment import im.vector.riotx.features.signout.soft.SoftLogoutFragment +import im.vector.riotx.features.terms.ReviewTermsFragment import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.UserDirectoryFragment -import im.vector.riotx.features.terms.ReviewTermsFragment @Module interface FragmentModule { @@ -308,6 +311,21 @@ interface FragmentModule { @FragmentKey(RoomMemberListFragment::class) fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(RoomUploadsFragment::class) + fun bindRoomUploadsFragment(fragment: RoomUploadsFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(RoomUploadsMediaFragment::class) + fun bindRoomUploadsMediaFragment(fragment: RoomUploadsMediaFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(RoomUploadsFilesFragment::class) + fun bindRoomUploadsFilesFragment(fragment: RoomUploadsFilesFragment): Fragment + @Binds @IntoMap @FragmentKey(RoomSettingsFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt index 1a9b268b90..bfc815f1ed 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt @@ -27,6 +27,7 @@ import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { @@ -66,7 +67,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { } private fun openRoomUploads() { - notImplemented("Open room uploads") + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs) } private fun openRoomSettings() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt new file mode 100644 index 0000000000..5191db30a6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class RoomUploadsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt new file mode 100644 index 0000000000..0a35d4cf08 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.roomprofile.RoomProfileArgs +import kotlinx.android.synthetic.main.fragment_room_uploads.* +import javax.inject.Inject + +class RoomUploadsFragment @Inject constructor( + private val viewModelFactory: RoomUploadsViewModel.Factory, + private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment(), RoomUploadsViewModel.Factory by viewModelFactory { + + private val roomProfileArgs: RoomProfileArgs by args() + + private val viewModel: RoomUploadsViewModel by fragmentViewModel() + + override fun getLayoutResId() = R.layout.fragment_room_uploads + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val sectionsPagerAdapter = RoomUploadsPagerAdapter(childFragmentManager, stringProvider) + view_pager.adapter = sectionsPagerAdapter + tabs.setupWithViewPager(view_pager) + + setupToolbar(matrixProfileToolbar) + + // Initialize your view, subscribe to viewModel... + } + + /* + override fun onDestroyView() { + super.onDestroyView() + // Clear your view, unsubscribe... + } + + */ + + override fun invalidate() = withState(viewModel) { state -> + renderRoomSummary(state) + } + + private fun renderRoomSummary(state: RoomUploadsViewState) { + state.roomSummary()?.let { + matrixProfileToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), matrixProfileToolbarAvatarImageView) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt new file mode 100644 index 0000000000..ae173a687a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment +import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment + +private val TAB_TITLES = arrayOf( + R.string.uploads_title_media, + R.string.uploads_title_files +) + +/** + * A [FragmentPagerAdapter] that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ +class RoomUploadsPagerAdapter( + fm: FragmentManager, + private val stringProvider: StringProvider +) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + + override fun getItem(position: Int): Fragment { + // getItem is called to instantiate the fragment for the given page. + // Return a PlaceholderFragment (defined as a static inner class below). + return if (position == 0) { + RoomUploadsMediaFragment() + } else { + RoomUploadsFilesFragment() + } + } + + override fun getPageTitle(position: Int): CharSequence? { + return stringProvider.getString(TAB_TITLES[position]) + } + + override fun getCount(): Int { + // Show 2 total pages. + return 2 + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt new file mode 100644 index 0000000000..3cf402bccf --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult +import im.vector.matrix.android.internal.util.awaitCallback +import im.vector.matrix.rx.rx +import im.vector.matrix.rx.unwrap +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel +import kotlinx.coroutines.launch + +class RoomUploadsViewModel @AssistedInject constructor( + @Assisted initialState: RoomUploadsViewState, + private val session: Session +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomUploadsViewState): RoomUploadsViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomUploadsViewState): RoomUploadsViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + private val room = session.getRoom(initialState.roomId)!! + + init { + observeRoomSummary() + // Send a first request + handleLoadMore() + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .unwrap() + .execute { async -> + copy(roomSummary = async) + } + } + + private fun handleLoadMore() = withState { state -> + if (state.asyncEventsRequest is Loading) return@withState + + setState { + copy( + asyncEventsRequest = Loading() + ) + } + + viewModelScope.launch { + try { + val result = awaitCallback { + room.getUploads(20, token, it) + } + + token = result.nextToken + + setState { + copy( + asyncEventsRequest = Uninitialized, + events = this.events + result.events, + hasMore = result.nextToken != null + ) + } + } catch (failure: Throwable) { + // TODO Post fail + setState { + copy( + asyncEventsRequest = Fail(failure) + ) + } + } + } + } + + private var token: String? = null + + override fun handle(action: RoomUploadsAction) { + // when (action) { +// + // }.exhaustive + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt new file mode 100644 index 0000000000..93b1b3814a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotx.features.roomprofile.RoomProfileArgs + +data class RoomUploadsViewState( + val roomId: String = "", + val roomSummary: Async = Uninitialized, + // Store cumul of pagination result, grouped by type + val mediaEvents: List = emptyList(), + val fileEvents: List = emptyList(), + // Current pagination request + val asyncEventsRequest: Async> = Uninitialized, + // True if more result are available server side + val hasMore: Boolean = false +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) +} + diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt new file mode 100644 index 0000000000..4f583ca7a1 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.child + +import com.airbnb.mvrx.parentFragmentViewModel +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel +import javax.inject.Inject + +/** + * A placeholder fragment containing a simple view. + */ +class RoomUploadsFilesFragment @Inject constructor() : VectorBaseFragment() { + + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) + + override fun getLayoutResId() = R.layout.fragment_generic_recycler +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt new file mode 100644 index 0000000000..eab4adab1c --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.child + +import com.airbnb.mvrx.parentFragmentViewModel +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel +import javax.inject.Inject + +/** + * A placeholder fragment containing a simple view. + */ +class RoomUploadsMediaFragment @Inject constructor() : VectorBaseFragment() { + + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) + + override fun getLayoutResId() = R.layout.fragment_generic_recycler +} diff --git a/vector/src/main/res/layout/fragment_room_uploads.xml b/vector/src/main/res/layout/fragment_room_uploads.xml new file mode 100644 index 0000000000..d3cbf08603 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_uploads.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0a9faa8bce..f5d161d901 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1788,6 +1788,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming "Sticker" Couldn\'t handle share data + MEDIA + There is no media in this room + FILES + There is no files in this room + "It's spam" "It's inappropriate" "Custom report…" From e9ca876444f31212b419ed2416a3f0d83b90eb4c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2020 17:39:10 +0200 Subject: [PATCH 44/59] Uploads: add screen - WIP --- .../android/api/session/events/model/Event.kt | 17 ++ .../session/room/uploads/GetUploadsResult.kt | 2 +- .../room/uploads/DefaultUploadsService.kt | 4 +- .../session/room/uploads/GetUploadsTask.kt | 2 +- .../im/vector/riotx/core/di/FragmentModule.kt | 4 +- .../epoxy/SquareLoadingItem.kt} | 17 +- .../riotx/core/utils/DimensionConverter.kt | 4 + .../home/room/detail/RoomDetailFragment.kt | 29 +-- .../features/media/ImageContentRenderer.kt | 11 ++ .../features/navigation/DefaultNavigator.kt | 30 ++++ .../riotx/features/navigation/Navigator.kt | 7 + .../roomprofile/uploads/RoomUploadsAction.kt | 8 +- .../uploads/RoomUploadsFragment.kt | 19 +- .../uploads/RoomUploadsPagerAdapter.kt | 42 ++--- .../uploads/RoomUploadsViewModel.kt | 11 +- .../uploads/files/RoomUploadsFilesFragment.kt | 73 ++++++++ .../uploads/files/UploadsFileController.kt | 102 +++++++++++ .../uploads/files/UploadsFileItem.kt | 57 ++++++ .../Config.kt} | 19 +- .../uploads/media/RoomUploadsMediaFragment.kt | 82 +++++++++ .../uploads/media/UploadsImageItem.kt | 49 ++++++ .../uploads/media/UploadsMediaController.kt | 165 ++++++++++++++++++ .../uploads/media/UploadsVideoItem.kt | 50 ++++++ .../main/res/layout/fragment_room_uploads.xml | 22 +-- .../main/res/layout/item_loading_square.xml | 23 +++ .../src/main/res/layout/item_uploads_file.xml | 75 ++++++++ .../main/res/layout/item_uploads_image.xml | 22 +++ .../main/res/layout/item_uploads_video.xml | 31 ++++ 28 files changed, 868 insertions(+), 109 deletions(-) rename vector/src/main/java/im/vector/riotx/{features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt => core/epoxy/SquareLoadingItem.kt} (53%) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileItem.kt rename vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/{child/RoomUploadsMediaFragment.kt => media/Config.kt} (50%) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt create mode 100644 vector/src/main/res/layout/item_loading_square.xml create mode 100644 vector/src/main/res/layout/item_uploads_file.xml create mode 100644 vector/src/main/res/layout/item_uploads_image.xml create mode 100644 vector/src/main/res/layout/item_uploads_video.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index a60d0fd9ac..3ea3345814 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -220,3 +220,20 @@ fun Event.isImageMessage(): Boolean { else -> false } } + +fun Event.isVideoMessage(): Boolean { + return getClearType() == EventType.MESSAGE + && when (getClearContent()?.toModel()?.msgType) { + MessageType.MSGTYPE_VIDEO -> true + else -> false + } +} + +fun Event.isPreviewableMessage(): Boolean { + return getClearType() == EventType.MESSAGE + && when (getClearContent()?.toModel()?.msgType) { + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_VIDEO -> true + else -> false + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt index b1e70932af..3b151d5701 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt @@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.uploads import im.vector.matrix.android.api.session.events.model.Event data class GetUploadsResult( - // List of fetched Events + // List of fetched Events, most recent first val events: List, // token to get more events, or null if there is no more event to fetch val nextToken: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt index e54a3b93fe..dd8269a079 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/DefaultUploadsService.kt @@ -38,7 +38,9 @@ internal class DefaultUploadsService @AssistedInject constructor( override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback): Cancelable { return getUploadsTask - .configureWith(GetUploadsTask.Params(roomId, numberOfEvents, since)) + .configureWith(GetUploadsTask.Params(roomId, numberOfEvents, since)) { + this.callback = callback + } .executeBy(taskExecutor) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt index 4bed591315..647fc57fff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -51,7 +51,7 @@ internal class DefaultGetUploadsTask @Inject constructor( } return GetUploadsResult( - events = chunk.events, // reverse? + events = chunk.events, nextToken = chunk.end?.takeIf { it != chunk.start } ) } diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index b7b0a1c2f4..7e0407fef4 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -77,8 +77,8 @@ import im.vector.riotx.features.roomprofile.RoomProfileFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment -import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment -import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment +import im.vector.riotx.features.roomprofile.uploads.files.RoomUploadsFilesFragment +import im.vector.riotx.features.roomprofile.uploads.media.RoomUploadsMediaFragment import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment import im.vector.riotx.features.settings.VectorSettingsLabsFragment diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/SquareLoadingItem.kt similarity index 53% rename from vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt rename to vector/src/main/java/im/vector/riotx/core/epoxy/SquareLoadingItem.kt index 4f583ca7a1..c0f6eb198f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/SquareLoadingItem.kt @@ -14,20 +14,13 @@ * limitations under the License. */ -package im.vector.riotx.features.roomprofile.uploads.child +package im.vector.riotx.core.epoxy -import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel -import javax.inject.Inject -/** - * A placeholder fragment containing a simple view. - */ -class RoomUploadsFilesFragment @Inject constructor() : VectorBaseFragment() { +@EpoxyModelClass(layout = R.layout.item_loading_square) +abstract class SquareLoadingItem : VectorEpoxyModel() { - private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) - - override fun getLayoutResId() = R.layout.fragment_generic_recycler + class Holder : VectorEpoxyHolder() } diff --git a/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt b/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt index 826d9a495a..4c2ff29874 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt @@ -36,4 +36,8 @@ class DimensionConverter @Inject constructor(val resources: Resources) { resources.displayMetrics ).toInt() } + + fun pdToDp(px: Int): Int { + return (px.toFloat() / resources.displayMetrics.density).toInt() + } } 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 079a2927d7..25c59e8f41 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 @@ -22,7 +22,6 @@ import android.content.DialogInterface import android.content.Intent import android.graphics.Typeface import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Parcelable import android.text.Spannable @@ -30,12 +29,10 @@ import android.view.HapticFeedbackConstants import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.Window import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog -import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.core.text.buildSpannedString @@ -150,7 +147,6 @@ import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.media.ImageContentRenderer -import im.vector.riotx.features.media.ImageMediaViewerActivity import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoMediaViewerActivity import im.vector.riotx.features.notifications.NotificationDrawerManager @@ -998,31 +994,14 @@ class RoomDetailFragment @Inject constructor( } override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) { - // TODO Use navigator - - val intent = ImageMediaViewerActivity.newIntent(vectorBaseActivity, mediaData, ViewCompat.getTransitionName(view)) - val pairs = ArrayList>() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - requireActivity().window.decorView.findViewById(android.R.id.statusBarBackground)?.let { - pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) - } - requireActivity().window.decorView.findViewById(android.R.id.navigationBarBackground)?.let { - pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) - } + navigator.openImageViewer(requireActivity(), mediaData, view) { pairs -> + pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) + pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) } - pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) - pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) - pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) - - val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation( - requireActivity(), *pairs.toTypedArray()).toBundle() - startActivity(intent, bundle) } override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) { - // TODO Use navigator - val intent = VideoMediaViewerActivity.newIntent(vectorBaseActivity, mediaData) - startActivity(intent) + navigator.openVideoViewer(requireActivity(), mediaData) } override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) { diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index becb714bf4..6756024aff 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -65,6 +65,17 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: STICKER } + /** + * For gallery + */ + fun render(data: Data, imageView: ImageView, size: Int) { + // a11y + imageView.contentDescription = data.filename + + createGlideRequest(data, Mode.THUMBNAIL, imageView, Size(size, size)) + .into(imageView) + } + fun render(data: Data, mode: Mode, imageView: ImageView) { val size = processSize(data, mode) imageView.layoutParams.width = size.width diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index b2213eb223..db12bcf61b 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -19,9 +19,12 @@ package im.vector.riotx.features.navigation import android.app.Activity import android.content.Context import android.content.Intent +import android.os.Build import android.view.View +import android.view.Window import androidx.core.app.ActivityOptionsCompat import androidx.core.app.TaskStackBuilder +import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction @@ -45,6 +48,10 @@ import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.invite.InviteUsersToRoomActivity import im.vector.riotx.features.media.BigImageViewerActivity +import im.vector.riotx.features.media.ImageContentRenderer +import im.vector.riotx.features.media.ImageMediaViewerActivity +import im.vector.riotx.features.media.VideoContentRenderer +import im.vector.riotx.features.media.VideoMediaViewerActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity @@ -215,6 +222,29 @@ class DefaultNavigator @Inject constructor( fragment.startActivityForResult(intent, requestCode) } + override fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) { + val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view)) + val pairs = ArrayList>() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.window.decorView.findViewById(android.R.id.statusBarBackground)?.let { + pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) + } + activity.window.decorView.findViewById(android.R.id.navigationBarBackground)?.let { + pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) + } + } + pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) + options?.invoke(pairs) + + val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() + activity.startActivity(intent, bundle) + } + + override fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) { + val intent = VideoMediaViewerActivity.newIntent(activity, mediaData) + activity.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 07ec0e4ca2..9323a87da5 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -19,10 +19,13 @@ package im.vector.riotx.features.navigation import android.app.Activity import android.content.Context import android.view.View +import androidx.core.util.Pair import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.util.MatrixItem +import im.vector.riotx.features.media.ImageContentRenderer +import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.terms.ReviewTermsActivity @@ -76,4 +79,8 @@ interface Navigator { baseUrl: String, token: String?, requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE) + + fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) + + fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt index 5191db30a6..24cb4f6bcb 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt @@ -16,6 +16,12 @@ package im.vector.riotx.features.roomprofile.uploads +import im.vector.matrix.android.api.session.events.model.Event import im.vector.riotx.core.platform.VectorViewModelAction -sealed class RoomUploadsAction : VectorViewModelAction +sealed class RoomUploadsAction : VectorViewModelAction { + data class Download(val event: Event) : RoomUploadsAction() + data class Share(val event: Event) : RoomUploadsAction() + + object Retry : RoomUploadsAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt index 0a35d4cf08..632965c864 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -21,6 +21,7 @@ import android.view.View import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import com.google.android.material.tabs.TabLayoutMediator import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment @@ -45,11 +46,17 @@ class RoomUploadsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val sectionsPagerAdapter = RoomUploadsPagerAdapter(childFragmentManager, stringProvider) - view_pager.adapter = sectionsPagerAdapter - tabs.setupWithViewPager(view_pager) + val sectionsPagerAdapter = RoomUploadsPagerAdapter(this) + roomUploadsViewPager.adapter = sectionsPagerAdapter - setupToolbar(matrixProfileToolbar) + TabLayoutMediator(roomUploadsTabs, roomUploadsViewPager) { tab, position -> + when (position) { + 0 -> tab.text = stringProvider.getString(R.string.uploads_media_title) + 1 -> tab.text = stringProvider.getString(R.string.uploads_files_title) + } + }.attach() + + setupToolbar(roomUploadsToolbar) // Initialize your view, subscribe to viewModel... } @@ -68,8 +75,8 @@ class RoomUploadsFragment @Inject constructor( private fun renderRoomSummary(state: RoomUploadsViewState) { state.roomSummary()?.let { - matrixProfileToolbarTitleView.text = it.displayName - avatarRenderer.render(it.toMatrixItem(), matrixProfileToolbarAvatarImageView) + roomUploadsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomUploadsToolbarAvatarImageView) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt index ae173a687a..6866d5e2fa 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsPagerAdapter.kt @@ -17,43 +17,21 @@ package im.vector.riotx.features.roomprofile.uploads import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import im.vector.riotx.R -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsFilesFragment -import im.vector.riotx.features.roomprofile.uploads.child.RoomUploadsMediaFragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import im.vector.riotx.features.roomprofile.uploads.files.RoomUploadsFilesFragment +import im.vector.riotx.features.roomprofile.uploads.media.RoomUploadsMediaFragment -private val TAB_TITLES = arrayOf( - R.string.uploads_title_media, - R.string.uploads_title_files -) - -/** - * A [FragmentPagerAdapter] that returns a fragment corresponding to - * one of the sections/tabs/pages. - */ class RoomUploadsPagerAdapter( - fm: FragmentManager, - private val stringProvider: StringProvider -) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + private val fragment: Fragment +) : FragmentStateAdapter(fragment) { - override fun getItem(position: Int): Fragment { - // getItem is called to instantiate the fragment for the given page. - // Return a PlaceholderFragment (defined as a static inner class below). + override fun getItemCount() = 2 + + override fun createFragment(position: Int): Fragment { return if (position == 0) { - RoomUploadsMediaFragment() + fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, RoomUploadsMediaFragment::class.java.name) } else { - RoomUploadsFilesFragment() + fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, RoomUploadsFilesFragment::class.java.name) } } - - override fun getPageTitle(position: Int): CharSequence? { - return stringProvider.getString(TAB_TITLES[position]) - } - - override fun getCount(): Int { - // Show 2 total pages. - return 2 - } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt index 3cf402bccf..5f7072aa58 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -27,6 +27,10 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.isPreviewableMessage +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.rx.rx @@ -90,10 +94,15 @@ class RoomUploadsViewModel @AssistedInject constructor( token = result.nextToken + val groupedEvents = result.events + .filter { it.getClearType() == EventType.MESSAGE && it.getClearContent()?.toModel() != null } + .groupBy { it.isPreviewableMessage() } + setState { copy( asyncEventsRequest = Uninitialized, - events = this.events + result.events, + mediaEvents = this.mediaEvents + groupedEvents[true].orEmpty(), + fileEvents = this.fileEvents + groupedEvents[false].orEmpty(), hasMore = result.nextToken != null ) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt new file mode 100644 index 0000000000..4aaddac0bd --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.files + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel +import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import javax.inject.Inject + +class RoomUploadsFilesFragment @Inject constructor( + private val controller: UploadsFileController +) : VectorBaseFragment(), UploadsFileController.Listener { + + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) + + override fun getLayoutResId() = R.layout.fragment_generic_recycler + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + recyclerView.configureWith(controller, showDivider = true) + controller.listener = this + } + + override fun onDestroyView() { + super.onDestroyView() + recyclerView.cleanup() + controller.listener = null + } + + override fun onOpenClicked(event: Event) { + // TODO + } + + override fun onRetry() { + uploadsViewModel.handle(RoomUploadsAction.Retry) + } + + override fun onDownloadClicked(event: Event) { + uploadsViewModel.handle(RoomUploadsAction.Download(event)) + } + + override fun onShareClicked(event: Event) { + uploadsViewModel.handle(RoomUploadsAction.Share(event)) + } + + override fun invalidate() = withState(uploadsViewModel) { state -> + controller.setData(state) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt new file mode 100644 index 0000000000..b5f5785d69 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.files + +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState +import javax.inject.Inject + +class UploadsFileController @Inject constructor( + private val errorFormatter: ErrorFormatter, + colorProvider: ColorProvider +) : TypedEpoxyController() { + + interface Listener { + fun onRetry() + fun onOpenClicked(event: Event) + fun onDownloadClicked(event: Event) + fun onShareClicked(event: Event) + } + + private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) + + var listener: Listener? = null + + init { + setData(null) + } + + override fun buildModels(data: RoomUploadsViewState?) { + data ?: return + + if (data.fileEvents.isEmpty()) { + when (data.asyncEventsRequest) { + is Loading -> { + loadingItem { + id("loading") + } + } + is Fail -> { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error)) + listener { listener?.onRetry() } + } + } + } + } else { + buildFileItems(data.fileEvents) + + if (data.hasMore) { + loadingItem { + id("loadMore") + } + } + } + } + + private fun buildFileItems(fileEvents: List) { + fileEvents.forEach { + uploadsFileItem { + id(it.eventId ?: "") + title(it.getClearType()) + subtitle(it.getSenderKey()) + listener(object : UploadsFileItem.Listener { + override fun onItemClicked() { + listener?.onOpenClicked(it) + } + + override fun onDownloadClicked() { + listener?.onDownloadClicked(it) + } + + override fun onShareClicked() { + listener?.onShareClicked(it) + } + }) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileItem.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileItem.kt new file mode 100644 index 0000000000..927672dd70 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileItem.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.files + +import android.view.View +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 + +@EpoxyModelClass(layout = R.layout.item_uploads_file) +abstract class UploadsFileItem : VectorEpoxyModel() { + + @EpoxyAttribute var title: String? = null + @EpoxyAttribute var subtitle: String? = null + + @EpoxyAttribute var listener: Listener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener { listener?.onItemClicked() } + holder.titleView.text = title + holder.subtitleView.setTextOrHide(subtitle) + holder.downloadView.setOnClickListener { listener?.onDownloadClicked() } + holder.shareView.setOnClickListener { listener?.onShareClicked() } + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.uploadsFileTitle) + val subtitleView by bind(R.id.uploadsFileSubtitle) + val downloadView by bind(R.id.uploadsFileActionDownload) + val shareView by bind(R.id.uploadsFileActionShare) + } + + interface Listener { + fun onItemClicked() + fun onDownloadClicked() + fun onShareClicked() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/Config.kt similarity index 50% rename from vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt rename to vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/Config.kt index eab4adab1c..50d4feff55 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/child/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/Config.kt @@ -14,20 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.roomprofile.uploads.child +package im.vector.riotx.features.roomprofile.uploads.media -import com.airbnb.mvrx.parentFragmentViewModel -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel -import javax.inject.Inject - -/** - * A placeholder fragment containing a simple view. - */ -class RoomUploadsMediaFragment @Inject constructor() : VectorBaseFragment() { - - private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) - - override fun getLayoutResId() = R.layout.fragment_generic_recycler -} +// Min image size. Size will be adjusted at runtime +const val IMAGE_SIZE_DP = 120 diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt new file mode 100644 index 0000000000..3b1fdaec85 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.media + +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.features.media.ImageContentRenderer +import im.vector.riotx.features.media.VideoContentRenderer +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel +import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import javax.inject.Inject + +class RoomUploadsMediaFragment @Inject constructor( + private val controller: UploadsMediaController, + private val dimensionConverter: DimensionConverter +) : VectorBaseFragment(), UploadsMediaController.Listener { + + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) + + override fun getLayoutResId() = R.layout.fragment_generic_recycler + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns()) + recyclerView.adapter = controller.adapter + recyclerView.setHasFixedSize(true) + + controller.listener = this + } + + private fun getNumberOfColumns(): Int { + val displayMetrics = DisplayMetrics() + requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) + return dimensionConverter.pdToDp(displayMetrics.widthPixels) / IMAGE_SIZE_DP + } + + override fun onDestroyView() { + super.onDestroyView() + recyclerView.cleanup() + controller.listener = null + } + + override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) { + navigator.openImageViewer(requireActivity(), mediaData, view, null) + } + + override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) { + navigator.openVideoViewer(requireActivity(), mediaData) + } + + override fun onRetry() { + uploadsViewModel.handle(RoomUploadsAction.Retry) + } + + override fun invalidate() = withState(uploadsViewModel) { state -> + controller.setData(state) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt new file mode 100644 index 0000000000..98026901cc --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.media + +import android.view.View +import android.widget.ImageView +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.media.ImageContentRenderer + +@EpoxyModelClass(layout = R.layout.item_uploads_image) +abstract class UploadsImageItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var imageContentRenderer: ImageContentRenderer + @EpoxyAttribute lateinit var data: ImageContentRenderer.Data + + @EpoxyAttribute var listener: Listener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } + imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP) + } + + class Holder : VectorEpoxyHolder() { + val imageView by bind(R.id.uploadsImagePreview) + } + + interface Listener { + fun onItemClicked(view: View, data: ImageContentRenderer.Data) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt new file mode 100644 index 0000000000..c80798790f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.media + +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.isImageMessage +import im.vector.matrix.android.api.session.events.model.isVideoMessage +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.api.session.room.model.message.getFileUrl +import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.epoxy.squareLoadingItem +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.features.media.ImageContentRenderer +import im.vector.riotx.features.media.VideoContentRenderer +import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState +import javax.inject.Inject + +class UploadsMediaController @Inject constructor( + private val errorFormatter: ErrorFormatter, + private val imageContentRenderer: ImageContentRenderer, + private val dimensionConverter: DimensionConverter, + colorProvider: ColorProvider +) : TypedEpoxyController() { + + interface Listener { + fun onRetry() + fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) + fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) + } + + private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) + + var listener: Listener? = null + + private val itemSize = dimensionConverter.dpToPx(64) + + init { + setData(null) + } + + override fun buildModels(data: RoomUploadsViewState?) { + data ?: return + + if (data.mediaEvents.isEmpty()) { + when (data.asyncEventsRequest) { + is Loading -> { + squareLoadingItem { + id("loading") + } + } + is Fail -> { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error)) + listener { listener?.onRetry() } + } + } + } + } else { + buildMediaItems(data.mediaEvents) + + if (data.hasMore) { + squareLoadingItem { + id("loadMore") + } + } + } + } + + private fun buildMediaItems(mediaEvents: List) { + mediaEvents.forEach { event -> + when { + event.isImageMessage() -> { + val data = event.toImageContentRendererData() ?: return@forEach + uploadsImageItem { + id(event.eventId ?: "") + imageContentRenderer(imageContentRenderer) + data(data) + listener(object : UploadsImageItem.Listener { + override fun onItemClicked(view: View, data: ImageContentRenderer.Data) { + listener?.onOpenImageClicked(view, data) + } + }) + } + } + event.isVideoMessage() -> { + val data = event.toVideoContentRendererData() ?: return@forEach + uploadsVideoItem { + id(event.eventId ?: "") + imageContentRenderer(imageContentRenderer) + data(data) + listener(object : UploadsVideoItem.Listener { + override fun onItemClicked(view: View, data: VideoContentRenderer.Data) { + listener?.onOpenVideoClicked(view, data) + } + }) + } + } + } + } + } + + private fun Event.toImageContentRendererData(): ImageContentRenderer.Data? { + val messageContent = getClearContent()?.toModel() ?: return null + + return ImageContentRenderer.Data( + eventId = eventId ?: "", + filename = messageContent.body, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + height = messageContent.info?.height, + maxHeight = itemSize, + width = messageContent.info?.width, + maxWidth = itemSize + ) + } + + private fun Event.toVideoContentRendererData(): VideoContentRenderer.Data? { + val messageContent = getClearContent()?.toModel() ?: return null + + val thumbnailData = ImageContentRenderer.Data( + eventId = eventId ?: "", + filename = messageContent.body, + url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl, + elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), + height = messageContent.videoInfo?.height, + maxHeight = itemSize, + width = messageContent.videoInfo?.width, + maxWidth = itemSize + ) + + return VideoContentRenderer.Data( + eventId = eventId ?: "", + filename = messageContent.body, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + thumbnailMediaData = thumbnailData + ) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt new file mode 100644 index 0000000000..82e33b76da --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads.media + +import android.view.View +import android.widget.ImageView +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.media.ImageContentRenderer +import im.vector.riotx.features.media.VideoContentRenderer + +@EpoxyModelClass(layout = R.layout.item_uploads_video) +abstract class UploadsVideoItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var imageContentRenderer: ImageContentRenderer + @EpoxyAttribute lateinit var data: VideoContentRenderer.Data + + @EpoxyAttribute var listener: Listener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } + imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP) + } + + class Holder : VectorEpoxyHolder() { + val imageView by bind(R.id.uploadsVideoPreview) + } + + interface Listener { + fun onItemClicked(view: View, data: VideoContentRenderer.Data) + } +} diff --git a/vector/src/main/res/layout/fragment_room_uploads.xml b/vector/src/main/res/layout/fragment_room_uploads.xml index d3cbf08603..36d779b533 100644 --- a/vector/src/main/res/layout/fragment_room_uploads.xml +++ b/vector/src/main/res/layout/fragment_room_uploads.xml @@ -12,7 +12,7 @@ android:elevation="4dp"> @@ -66,18 +66,18 @@ - diff --git a/vector/src/main/res/layout/item_loading_square.xml b/vector/src/main/res/layout/item_loading_square.xml new file mode 100644 index 0000000000..7596c39fc6 --- /dev/null +++ b/vector/src/main/res/layout/item_loading_square.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_uploads_file.xml b/vector/src/main/res/layout/item_uploads_file.xml new file mode 100644 index 0000000000..9232583a28 --- /dev/null +++ b/vector/src/main/res/layout/item_uploads_file.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_uploads_image.xml b/vector/src/main/res/layout/item_uploads_image.xml new file mode 100644 index 0000000000..8ea0506bce --- /dev/null +++ b/vector/src/main/res/layout/item_uploads_image.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_uploads_video.xml b/vector/src/main/res/layout/item_uploads_video.xml new file mode 100644 index 0000000000..6e19326da9 --- /dev/null +++ b/vector/src/main/res/layout/item_uploads_video.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file From 88cba74cacfc60028833584355e5adac26afd6b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2020 18:43:57 +0200 Subject: [PATCH 45/59] Uploads: add screen - WIP --- .../session/room/uploads/GetUploadsResult.kt | 6 ++-- .../session/room/timeline/TokenChunkEvent.kt | 2 ++ .../room/timeline/TokenChunkEventPersistor.kt | 2 +- .../session/room/uploads/GetUploadsTask.kt | 3 +- .../roomprofile/uploads/RoomUploadsAction.kt | 1 + .../uploads/RoomUploadsViewModel.kt | 17 ++++++---- .../uploads/RoomUploadsViewState.kt | 4 +-- .../uploads/files/RoomUploadsFilesFragment.kt | 7 ++++ .../uploads/files/UploadsFileController.kt | 29 ++++++++++++++--- .../uploads/media/RoomUploadsMediaFragment.kt | 7 ++++ .../uploads/media/UploadsMediaController.kt | 32 +++++++++++++++---- 11 files changed, 88 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt index 3b151d5701..3e194beae5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt @@ -21,6 +21,8 @@ import im.vector.matrix.android.api.session.events.model.Event data class GetUploadsResult( // List of fetched Events, most recent first val events: List, - // token to get more events, or null if there is no more event to fetch - val nextToken: String? + // token to get more events + val nextToken: String, + // True if there are more event to load + val hasMore: Boolean ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt index 95edf9bc49..7344f5598b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEvent.kt @@ -23,4 +23,6 @@ internal interface TokenChunkEvent { val end: String? val events: List val stateEvents: List + + fun hasMore() = start != end } 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 f7411b3bf1..e0f5b106d6 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 @@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy } ?: ChunkEntity.create(realm, prevToken, nextToken) - if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) { + if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { handleReachEnd(realm, roomId, direction, currentChunk) } else { handlePagination(realm, roomId, direction, receivedChunk, currentChunk) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt index 647fc57fff..d51c72046f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -52,7 +52,8 @@ internal class DefaultGetUploadsTask @Inject constructor( return GetUploadsResult( events = chunk.events, - nextToken = chunk.end?.takeIf { it != chunk.start } + nextToken = chunk.end ?: "", + hasMore = chunk.hasMore() ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt index 24cb4f6bcb..d817beb3e2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt @@ -24,4 +24,5 @@ sealed class RoomUploadsAction : VectorViewModelAction { data class Share(val event: Event) : RoomUploadsAction() object Retry : RoomUploadsAction() + object LoadMore : RoomUploadsAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt index 5f7072aa58..0af41ff4b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import kotlinx.coroutines.launch @@ -79,6 +80,7 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleLoadMore() = withState { state -> if (state.asyncEventsRequest is Loading) return@withState + if (!state.hasMore) return@withState setState { copy( @@ -100,10 +102,10 @@ class RoomUploadsViewModel @AssistedInject constructor( setState { copy( - asyncEventsRequest = Uninitialized, + asyncEventsRequest = Success(Unit), mediaEvents = this.mediaEvents + groupedEvents[true].orEmpty(), fileEvents = this.fileEvents + groupedEvents[false].orEmpty(), - hasMore = result.nextToken != null + hasMore = result.hasMore ) } } catch (failure: Throwable) { @@ -120,8 +122,11 @@ class RoomUploadsViewModel @AssistedInject constructor( private var token: String? = null override fun handle(action: RoomUploadsAction) { - // when (action) { -// - // }.exhaustive + when (action) { + is RoomUploadsAction.Download -> TODO() + is RoomUploadsAction.Share -> TODO() + RoomUploadsAction.Retry -> handleLoadMore() + RoomUploadsAction.LoadMore -> handleLoadMore() + }.exhaustive } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt index 93b1b3814a..31c4e937c8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt @@ -30,9 +30,9 @@ data class RoomUploadsViewState( val mediaEvents: List = emptyList(), val fileEvents: List = emptyList(), // Current pagination request - val asyncEventsRequest: Async> = Uninitialized, + val asyncEventsRequest: Async = Uninitialized, // True if more result are available server side - val hasMore: Boolean = false + val hasMore: Boolean = true ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 4aaddac0bd..10f3625545 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.files import android.os.Bundle import android.view.View +import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.events.model.Event @@ -41,6 +42,8 @@ class RoomUploadsFilesFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val epoxyVisibilityTracker = EpoxyVisibilityTracker() + epoxyVisibilityTracker.attach(recyclerView) recyclerView.configureWith(controller, showDivider = true) controller.listener = this } @@ -59,6 +62,10 @@ class RoomUploadsFilesFragment @Inject constructor( uploadsViewModel.handle(RoomUploadsAction.Retry) } + override fun loadMore() { + uploadsViewModel.handle(RoomUploadsAction.LoadMore) + } + override fun onDownloadClicked(event: Event) { uploadsViewModel.handle(RoomUploadsAction.Download(event)) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index b5f5785d69..e27be153c8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -17,31 +17,33 @@ package im.vector.riotx.features.roomprofile.uploads.files import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.events.model.Event import im.vector.riotx.R import im.vector.riotx.core.epoxy.errorWithRetryItem import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.epoxy.noResultItem import im.vector.riotx.core.error.ErrorFormatter -import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState import javax.inject.Inject class UploadsFileController @Inject constructor( private val errorFormatter: ErrorFormatter, - colorProvider: ColorProvider + private val stringProvider: StringProvider ) : TypedEpoxyController() { interface Listener { fun onRetry() + fun loadMore() fun onOpenClicked(event: Event) fun onDownloadClicked(event: Event) fun onShareClicked(event: Event) } - private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) - var listener: Listener? = null init { @@ -65,6 +67,20 @@ class UploadsFileController @Inject constructor( listener { listener?.onRetry() } } } + is Success -> { + if (data.hasMore) { + // We need to load more items + listener?.loadMore() + loadingItem { + id("loading") + } + } else { + noResultItem { + id("noResult") + text(stringProvider.getString(R.string.uploads_files_no_result)) + } + } + } } } else { buildFileItems(data.fileEvents) @@ -72,6 +88,11 @@ class UploadsFileController @Inject constructor( if (data.hasMore) { loadingItem { id("loadMore") + onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + listener?.loadMore() + } + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index 3b1fdaec85..0446778a11 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.util.DisplayMetrics import android.view.View import androidx.recyclerview.widget.GridLayoutManager +import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -45,6 +46,8 @@ class RoomUploadsMediaFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val epoxyVisibilityTracker = EpoxyVisibilityTracker() + epoxyVisibilityTracker.attach(recyclerView) recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns()) recyclerView.adapter = controller.adapter recyclerView.setHasFixedSize(true) @@ -72,6 +75,10 @@ class RoomUploadsMediaFragment @Inject constructor( navigator.openVideoViewer(requireActivity(), mediaData) } + override fun loadMore() { + uploadsViewModel.handle(RoomUploadsAction.LoadMore) + } + override fun onRetry() { uploadsViewModel.handle(RoomUploadsAction.Retry) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt index c80798790f..41711359d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -18,22 +18,24 @@ package im.vector.riotx.features.roomprofile.uploads.media import android.view.View import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.isImageMessage import im.vector.matrix.android.api.session.events.model.isVideoMessage import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.riotx.R import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.epoxy.noResultItem import im.vector.riotx.core.epoxy.squareLoadingItem import im.vector.riotx.core.error.ErrorFormatter -import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer @@ -43,18 +45,17 @@ import javax.inject.Inject class UploadsMediaController @Inject constructor( private val errorFormatter: ErrorFormatter, private val imageContentRenderer: ImageContentRenderer, - private val dimensionConverter: DimensionConverter, - colorProvider: ColorProvider + private val stringProvider: StringProvider, + dimensionConverter: DimensionConverter ) : TypedEpoxyController() { interface Listener { fun onRetry() fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) + fun loadMore() } - private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) - var listener: Listener? = null private val itemSize = dimensionConverter.dpToPx(64) @@ -80,6 +81,20 @@ class UploadsMediaController @Inject constructor( listener { listener?.onRetry() } } } + is Success -> { + if (data.hasMore) { + // We need to load more items + listener?.loadMore() + squareLoadingItem { + id("loading") + } + } else { + noResultItem { + id("noResult") + text(stringProvider.getString(R.string.uploads_media_no_result)) + } + } + } } } else { buildMediaItems(data.mediaEvents) @@ -87,6 +102,11 @@ class UploadsMediaController @Inject constructor( if (data.hasMore) { squareLoadingItem { id("loadMore") + onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + listener?.loadMore() + } + } } } } From 919225bdfd56dd7ca65b9b9c2c9d0ffd5a1d7413 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2020 18:49:29 +0200 Subject: [PATCH 46/59] Uploads: create extension --- .../java/im/vector/riotx/core/extensions/RecyclerView.kt | 3 +++ .../riotx/features/home/room/detail/RoomDetailFragment.kt | 6 ++---- .../riotx/features/roomdirectory/PublicRoomsFragment.kt | 5 ++--- .../roomprofile/uploads/files/RoomUploadsFilesFragment.kt | 5 ++--- .../roomprofile/uploads/media/RoomUploadsMediaFragment.kt | 5 ++--- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index 3b3132229c..3762c52d45 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -21,6 +21,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyVisibilityTracker import im.vector.riotx.R import im.vector.riotx.features.themes.ThemeUtils @@ -61,3 +62,5 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController, fun RecyclerView.cleanup() { adapter = null } + +fun RecyclerView.trackItemsVisibilityChange() = EpoxyVisibilityTracker().attach(this) 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 25c59e8f41..bd9a4c9d7e 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 @@ -46,7 +46,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import com.airbnb.epoxy.EpoxyModel -import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -92,6 +91,7 @@ import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.showKeyboard +import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.files.addEntryToDownloadManager import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.intent.getMimeTypeFromUri @@ -148,7 +148,6 @@ import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer -import im.vector.riotx.features.media.VideoMediaViewerActivity import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.permalink.NavigationInterceptor import im.vector.riotx.features.permalink.PermalinkHandler @@ -539,8 +538,7 @@ class RoomDetailFragment @Inject constructor( timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline - val epoxyVisibilityTracker = EpoxyVisibilityTracker() - epoxyVisibilityTracker.attach(recyclerView) + recyclerView.trackItemsVisibilityChange() layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index a75479275b..869ee85337 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.roomdirectory import android.os.Bundle import android.view.MenuItem import android.view.View -import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.snackbar.Snackbar @@ -29,6 +28,7 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.platform.VectorBaseFragment import io.reactivex.rxkotlin.subscribeBy import kotlinx.android.synthetic.main.fragment_public_rooms.* @@ -107,8 +107,7 @@ class PublicRoomsFragment @Inject constructor( } private fun setupRecyclerView() { - val epoxyVisibilityTracker = EpoxyVisibilityTracker() - epoxyVisibilityTracker.attach(publicRoomsList) + publicRoomsList.trackItemsVisibilityChange() publicRoomsList.configureWith(publicRoomsController) publicRoomsController.callback = this } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 10f3625545..1a27fa7b12 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -18,13 +18,13 @@ package im.vector.riotx.features.roomprofile.uploads.files import android.os.Bundle import android.view.View -import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.events.model.Event import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel @@ -42,8 +42,7 @@ class RoomUploadsFilesFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val epoxyVisibilityTracker = EpoxyVisibilityTracker() - epoxyVisibilityTracker.attach(recyclerView) + recyclerView.trackItemsVisibilityChange() recyclerView.configureWith(controller, showDivider = true) controller.listener = this } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index 0446778a11..5ac31168ad 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -20,11 +20,11 @@ import android.os.Bundle import android.util.DisplayMetrics import android.view.View import androidx.recyclerview.widget.GridLayoutManager -import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.media.ImageContentRenderer @@ -46,8 +46,7 @@ class RoomUploadsMediaFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val epoxyVisibilityTracker = EpoxyVisibilityTracker() - epoxyVisibilityTracker.attach(recyclerView) + recyclerView.trackItemsVisibilityChange() recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns()) recyclerView.adapter = controller.adapter recyclerView.setHasFixedSize(true) From f7de2f0f13046326a524ffc6595d40976300b8c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2020 19:26:44 +0200 Subject: [PATCH 47/59] Uploads: create extension --- .../core/utils/ExternalApplicationsUtil.kt | 6 ++- .../roomprofile/uploads/RoomUploadsAction.kt | 5 +- .../uploads/RoomUploadsFragment.kt | 28 +++++++++- .../uploads/RoomUploadsViewEvents.kt | 27 ++++++++++ .../uploads/RoomUploadsViewModel.kt | 51 +++++++++++++++++-- .../uploads/files/RoomUploadsFilesFragment.kt | 11 ++-- .../uploads/files/UploadsFileController.kt | 23 ++++++--- vector/src/main/res/values/strings.xml | 2 + 8 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewEvents.kt diff --git a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt index afb7c4586a..e46d756523 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt @@ -256,7 +256,11 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) { sendIntent.type = mediaMimeType sendIntent.putExtra(Intent.EXTRA_STREAM, mediaUri) - context.startActivity(sendIntent) + try { + context.startActivity(sendIntent) + } catch (activityNotFoundException: ActivityNotFoundException) { + context.toast(R.string.error_no_external_application_found) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt index d817beb3e2..667e5ab770 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt @@ -17,11 +17,12 @@ package im.vector.riotx.features.roomprofile.uploads import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomUploadsAction : VectorViewModelAction { - data class Download(val event: Event) : RoomUploadsAction() - data class Share(val event: Event) : RoomUploadsAction() + data class Download(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction() + data class Share(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction() object Retry : RoomUploadsAction() object LoadMore : RoomUploadsAction() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt index 632965c864..9ef2688b17 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -18,14 +18,20 @@ package im.vector.riotx.features.roomprofile.uploads import android.os.Bundle import android.view.View +import android.widget.Toast +import androidx.core.net.toUri import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.intent.getMimeTypeFromUri import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.utils.saveMedia +import im.vector.riotx.core.utils.shareMedia import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs import kotlinx.android.synthetic.main.fragment_room_uploads.* @@ -58,7 +64,27 @@ class RoomUploadsFragment @Inject constructor( setupToolbar(roomUploadsToolbar) - // Initialize your view, subscribe to viewModel... + viewModel.observeViewEvents { + when (it) { + is RoomUploadsViewEvents.FileReadyForSharing -> { + shareMedia(requireContext(), it.file, getMimeTypeFromUri(requireContext(), it.file.toUri())) + } + is RoomUploadsViewEvents.FileReadyForSaving -> { + val saved = saveMedia( + context = requireContext(), + file = it.file, + title = it.title, + mediaMimeType = getMimeTypeFromUri(requireContext(), it.file.toUri()) + ) + if (saved) { + Toast.makeText(requireContext(), R.string.media_file_added_to_gallery, Toast.LENGTH_LONG).show() + } else { + Toast.makeText(requireContext(), R.string.error_adding_media_file_to_gallery, Toast.LENGTH_LONG).show() + } + } + is RoomUploadsViewEvents.Failure -> showFailure(it.throwable) + }.exhaustive + } } /* diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewEvents.kt new file mode 100644 index 0000000000..cd0c34494d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewEvents.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 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.roomprofile.uploads + +import im.vector.riotx.core.platform.VectorViewEvents +import java.io.File + +sealed class RoomUploadsViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : RoomUploadsViewEvents() + + data class FileReadyForSharing(val file: File) : RoomUploadsViewEvents() + data class FileReadyForSaving(val file: File, val title: String) : RoomUploadsViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt index 0af41ff4b9..3239f88ace 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -30,20 +30,23 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isPreviewableMessage import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult +import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.core.extensions.exhaustive -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import kotlinx.coroutines.launch +import java.io.File class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, private val session: Session -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -123,10 +126,50 @@ class RoomUploadsViewModel @AssistedInject constructor( override fun handle(action: RoomUploadsAction) { when (action) { - is RoomUploadsAction.Download -> TODO() - is RoomUploadsAction.Share -> TODO() + is RoomUploadsAction.Download -> handleDownload(action) + is RoomUploadsAction.Share -> handleShare(action) RoomUploadsAction.Retry -> handleLoadMore() RoomUploadsAction.LoadMore -> handleLoadMore() }.exhaustive } + + private fun handleShare(action: RoomUploadsAction.Share) { + viewModelScope.launch { + try { + val file = awaitCallback { + session.downloadFile( + FileService.DownloadMode.FOR_EXTERNAL_SHARE, + action.event.eventId ?: "", + action.messageContent.body, + action.messageContent.getFileUrl(), + action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + it + ) + } + _viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file)) + } catch (failure: Throwable) { + _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) + } + } + } + + private fun handleDownload(action: RoomUploadsAction.Download) { + viewModelScope.launch { + try { + val file = awaitCallback { + session.downloadFile( + FileService.DownloadMode.FOR_EXTERNAL_SHARE, + action.event.eventId ?: "", + action.messageContent.body, + action.messageContent.getFileUrl(), + action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + it) + + } + _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.messageContent.body)) + } catch (failure: Throwable) { + _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) + } + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 1a27fa7b12..2798cdc683 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -21,6 +21,7 @@ import android.view.View import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -54,7 +55,7 @@ class RoomUploadsFilesFragment @Inject constructor( } override fun onOpenClicked(event: Event) { - // TODO + TODO() } override fun onRetry() { @@ -65,12 +66,12 @@ class RoomUploadsFilesFragment @Inject constructor( uploadsViewModel.handle(RoomUploadsAction.LoadMore) } - override fun onDownloadClicked(event: Event) { - uploadsViewModel.handle(RoomUploadsAction.Download(event)) + override fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) { + uploadsViewModel.handle(RoomUploadsAction.Download(event, messageWithAttachmentContent)) } - override fun onShareClicked(event: Event) { - uploadsViewModel.handle(RoomUploadsAction.Share(event)) + override fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) { + uploadsViewModel.handle(RoomUploadsAction.Share(event, messageWithAttachmentContent)) } override fun invalidate() = withState(uploadsViewModel) { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index e27be153c8..e37d2e0c15 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -22,7 +22,11 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading 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.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.riotx.R +import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.errorWithRetryItem import im.vector.riotx.core.epoxy.loadingItem import im.vector.riotx.core.epoxy.noResultItem @@ -33,15 +37,16 @@ import javax.inject.Inject class UploadsFileController @Inject constructor( private val errorFormatter: ErrorFormatter, - private val stringProvider: StringProvider + private val stringProvider: StringProvider, + private val dateFormatter: VectorDateFormatter ) : TypedEpoxyController() { interface Listener { fun onRetry() fun loadMore() fun onOpenClicked(event: Event) - fun onDownloadClicked(event: Event) - fun onShareClicked(event: Event) + fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) + fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) } var listener: Listener? = null @@ -100,21 +105,25 @@ class UploadsFileController @Inject constructor( private fun buildFileItems(fileEvents: List) { fileEvents.forEach { + val messageContent = it.getClearContent()?.toModel() ?: return@forEach + val messageWithAttachmentContent = (messageContent as? MessageWithAttachmentContent) ?: return@forEach + uploadsFileItem { id(it.eventId ?: "") - title(it.getClearType()) - subtitle(it.getSenderKey()) + title(messageWithAttachmentContent.body) + // TODO Resolve user displayName + subtitle(stringProvider.getString(R.string.uploads_files_subtitle, it.senderId, dateFormatter.formatRelativeDateTime(it.originServerTs))) listener(object : UploadsFileItem.Listener { override fun onItemClicked() { listener?.onOpenClicked(it) } override fun onDownloadClicked() { - listener?.onDownloadClicked(it) + listener?.onDownloadClicked(it, messageWithAttachmentContent) } override fun onShareClicked() { - listener?.onShareClicked(it) + listener?.onShareClicked(it, messageWithAttachmentContent) } }) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f5d161d901..b0ddce5ed3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1791,6 +1791,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming MEDIA There is no media in this room FILES + + %1$s at %2$s There is no files in this room "It's spam" From a2b366ebfed5dd29e46b0e779d61d478efc541b8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 08:56:29 +0200 Subject: [PATCH 48/59] Uploads: add placeholder for images --- .../features/media/ImageContentRenderer.kt | 2 ++ vector/src/main/res/drawable/ic_image.xml | 29 +++++++++++++++++++ .../main/res/layout/item_uploads_image.xml | 2 +- .../main/res/layout/item_uploads_video.xml | 2 +- 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_image.xml diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index 6756024aff..ab047fba0d 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -29,6 +29,7 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ORIENTATION import com.github.piasy.biv.view.BigImageView import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt +import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideRequest @@ -73,6 +74,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: imageView.contentDescription = data.filename createGlideRequest(data, Mode.THUMBNAIL, imageView, Size(size, size)) + .placeholder(R.drawable.ic_image) .into(imageView) } diff --git a/vector/src/main/res/drawable/ic_image.xml b/vector/src/main/res/drawable/ic_image.xml new file mode 100644 index 0000000000..70bc4a73a6 --- /dev/null +++ b/vector/src/main/res/drawable/ic_image.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/vector/src/main/res/layout/item_uploads_image.xml b/vector/src/main/res/layout/item_uploads_image.xml index 8ea0506bce..464816d74a 100644 --- a/vector/src/main/res/layout/item_uploads_image.xml +++ b/vector/src/main/res/layout/item_uploads_image.xml @@ -11,7 +11,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="2dp" - android:scaleType="centerCrop" + android:scaleType="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintEnd_toEndOf="parent" diff --git a/vector/src/main/res/layout/item_uploads_video.xml b/vector/src/main/res/layout/item_uploads_video.xml index 6e19326da9..97d7529c13 100644 --- a/vector/src/main/res/layout/item_uploads_video.xml +++ b/vector/src/main/res/layout/item_uploads_video.xml @@ -11,7 +11,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="2dp" - android:scaleType="centerCrop" + android:scaleType="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintEnd_toEndOf="parent" From e3ed3e5b05983b8fc222e06ef992e7f03d8ebc29 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 09:08:42 +0200 Subject: [PATCH 49/59] Uploads: cleanup --- vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt | 4 +++- .../java/im/vector/riotx/core/utils/DimensionConverter.kt | 5 ++++- .../roomprofile/uploads/media/RoomUploadsMediaFragment.kt | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt b/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt index 65ab0ad2b2..4a07bb2cea 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt @@ -16,5 +16,7 @@ package im.vector.riotx.core.ui.model +import androidx.annotation.Px + // android.util.Size in API 21+ -data class Size(val width: Int, val height: Int) +data class Size(@Px val width: Int, @Px val height: Int) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt b/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt index 4c2ff29874..01cd6a4f8f 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/DimensionConverter.kt @@ -17,10 +17,12 @@ package im.vector.riotx.core.utils import android.content.res.Resources import android.util.TypedValue +import androidx.annotation.Px import javax.inject.Inject class DimensionConverter @Inject constructor(val resources: Resources) { + @Px fun dpToPx(dp: Int): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, @@ -29,6 +31,7 @@ class DimensionConverter @Inject constructor(val resources: Resources) { ).toInt() } + @Px fun spToPx(sp: Int): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, @@ -37,7 +40,7 @@ class DimensionConverter @Inject constructor(val resources: Resources) { ).toInt() } - fun pdToDp(px: Int): Int { + fun pxToDp(@Px px: Int): Int { return (px.toFloat() / resources.displayMetrics.density).toInt() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index 5ac31168ad..a722c8281f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -57,7 +57,7 @@ class RoomUploadsMediaFragment @Inject constructor( private fun getNumberOfColumns(): Int { val displayMetrics = DisplayMetrics() requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - return dimensionConverter.pdToDp(displayMetrics.widthPixels) / IMAGE_SIZE_DP + return dimensionConverter.pxToDp(displayMetrics.widthPixels) / IMAGE_SIZE_DP } override fun onDestroyView() { From 907a786b1a01d246918fb487fcd7078a32125bd2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 09:16:31 +0200 Subject: [PATCH 50/59] Uploads: load element until loader not displayed anymore --- .../roomprofile/uploads/files/UploadsFileController.kt | 5 ++++- .../roomprofile/uploads/media/UploadsMediaController.kt | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index e37d2e0c15..0ad173be88 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -51,6 +51,8 @@ class UploadsFileController @Inject constructor( var listener: Listener? = null + private var idx = 0 + init { setData(null) } @@ -92,7 +94,8 @@ class UploadsFileController @Inject constructor( if (data.hasMore) { loadingItem { - id("loadMore") + // Always use a different id, because we can be notified several times of visibility state changed + id("loadMore${idx++}") onVisibilityStateChanged { _, _, visibilityState -> if (visibilityState == VisibilityState.VISIBLE) { listener?.loadMore() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt index 41711359d2..c6e3666fb5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -58,7 +58,9 @@ class UploadsMediaController @Inject constructor( var listener: Listener? = null - private val itemSize = dimensionConverter.dpToPx(64) + private var idx = 0 + + private val itemSize = dimensionConverter.dpToPx(IMAGE_SIZE_DP) init { setData(null) @@ -101,7 +103,8 @@ class UploadsMediaController @Inject constructor( if (data.hasMore) { squareLoadingItem { - id("loadMore") + // Always use a different id, because we can be notified several times of visibility state changed + id("loadMore${idx++}") onVisibilityStateChanged { _, _, visibilityState -> if (visibilityState == VisibilityState.VISIBLE) { listener?.loadMore() From f3a5fb7fe3eda015802873eabbc585239d62e857 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 11:56:54 +0200 Subject: [PATCH 51/59] Uploads: rework: provide information about the sender --- .../android/api/session/events/model/Event.kt | 9 ---- .../session/room/uploads/GetUploadsResult.kt | 5 +-- .../api/session/room/uploads/UploadEvent.kt | 31 +++++++++++++ .../session/room/uploads/UploadSenderInfo.kt | 33 ++++++++++++++ .../membership/RoomDisplayNameResolver.kt | 1 + .../session/room/uploads/GetUploadsTask.kt | 44 ++++++++++++++++++- .../roomprofile/uploads/RoomUploadsAction.kt | 7 ++- .../uploads/RoomUploadsViewModel.kt | 29 ++++++------ .../uploads/RoomUploadsViewState.kt | 6 +-- .../uploads/files/RoomUploadsFilesFragment.kt | 16 +++---- .../uploads/files/UploadsFileController.kt | 33 ++++++-------- .../uploads/media/UploadsMediaController.kt | 38 ++++++++-------- 12 files changed, 170 insertions(+), 82 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 3ea3345814..d3780ebe60 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -228,12 +228,3 @@ fun Event.isVideoMessage(): Boolean { else -> false } } - -fun Event.isPreviewableMessage(): Boolean { - return getClearType() == EventType.MESSAGE - && when (getClearContent()?.toModel()?.msgType) { - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_VIDEO -> true - else -> false - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt index 3e194beae5..bbe8641edd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt @@ -16,13 +16,12 @@ package im.vector.matrix.android.api.session.room.uploads -import im.vector.matrix.android.api.session.events.model.Event - data class GetUploadsResult( // List of fetched Events, most recent first - val events: List, + val events: List, // token to get more events val nextToken: String, // True if there are more event to load val hasMore: Boolean ) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt new file mode 100644 index 0000000000..93ce59a233 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.uploads + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent + +/** + * Wrapper around on Event. + * Similar to [im.vector.matrix.android.api.session.room.timeline.TimelineEvent], contains an Event with extra useful data + */ +data class UploadEvent( + val root: Event, + val eventId: String, + val contentWithAttachmentContent: MessageWithAttachmentContent, + val uploadSenderInfo: UploadSenderInfo +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt new file mode 100644 index 0000000000..0fa4b2cb68 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.uploads + +// TODO Maybe use this model for TimelineEvent as well +data class UploadSenderInfo( + val senderId: String, + val senderName: String?, + val isUniqueDisplayName: Boolean, + val senderAvatar: String? +) { + fun getDisambiguatedDisplayName(): String { + return when { + senderName.isNullOrBlank() -> senderId + isUniqueDisplayName -> senderName + else -> "$senderName (${senderId})" + } + } +} 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 feb05a3275..e7a68b3b5b 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 @@ -126,6 +126,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: return name ?: roomId } + /** See [im.vector.matrix.android.api.session.room.timeline.TimelineEvent.getDisambiguatedDisplayName] */ private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?, roomMemberHelper: RoomMemberHelper): String? { if (roomMemberSummary == null) return null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt index d51c72046f..f6fd2a442b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -16,10 +16,17 @@ package im.vector.matrix.android.internal.session.room.uploads +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult +import im.vector.matrix.android.api.session.room.uploads.UploadEvent +import im.vector.matrix.android.api.session.room.uploads.UploadSenderInfo import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterFactory import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.sync.SyncTokenStore @@ -39,6 +46,7 @@ internal interface GetUploadsTask : Task() + + val cacheOfSenderInfos = mutableMapOf() + + // Get a snapshot of all room members + monarchy.doWithRealm { realm -> + val roomMemberHelper = RoomMemberHelper(realm, params.roomId) + + uploadEvents = chunk.events.mapNotNull { event -> + val eventId = event.eventId ?: return@mapNotNull null + val messageContent = event.getClearContent()?.toModel() ?: return@mapNotNull null + val messageWithAttachmentContent = (messageContent as? MessageWithAttachmentContent) ?: return@mapNotNull null + val senderId = event.senderId ?: return@mapNotNull null + + val senderInfo = cacheOfSenderInfos.getOrPut(senderId) { + val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(senderId) + UploadSenderInfo( + senderId = senderId, + senderName = roomMemberSummaryEntity?.displayName, + isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName), + senderAvatar = roomMemberSummaryEntity?.avatarUrl + ) + } + + UploadEvent( + root = event, + eventId = eventId, + contentWithAttachmentContent = messageWithAttachmentContent, + uploadSenderInfo = senderInfo + ) + } + } + + return GetUploadsResult( - events = chunk.events, + events = uploadEvents, nextToken = chunk.end ?: "", hasMore = chunk.hasMore() ) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt index 667e5ab770..59571de122 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsAction.kt @@ -16,13 +16,12 @@ package im.vector.riotx.features.roomprofile.uploads -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent +import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomUploadsAction : VectorViewModelAction { - data class Download(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction() - data class Share(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction() + data class Download(val uploadEvent: UploadEvent) : RoomUploadsAction() + data class Share(val uploadEvent: UploadEvent) : RoomUploadsAction() object Retry : RoomUploadsAction() object LoadMore : RoomUploadsAction() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt index 3239f88ace..dcb0963ef0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -27,11 +27,8 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.isPreviewableMessage -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.file.FileService -import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt @@ -100,8 +97,10 @@ class RoomUploadsViewModel @AssistedInject constructor( token = result.nextToken val groupedEvents = result.events - .filter { it.getClearType() == EventType.MESSAGE && it.getClearContent()?.toModel() != null } - .groupBy { it.isPreviewableMessage() } + .groupBy { + it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_IMAGE + || it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_VIDEO + } setState { copy( @@ -139,10 +138,10 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.downloadFile( FileService.DownloadMode.FOR_EXTERNAL_SHARE, - action.event.eventId ?: "", - action.messageContent.body, - action.messageContent.getFileUrl(), - action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + action.uploadEvent.eventId, + action.uploadEvent.contentWithAttachmentContent.body, + action.uploadEvent.contentWithAttachmentContent.getFileUrl(), + action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), it ) } @@ -159,14 +158,14 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.downloadFile( FileService.DownloadMode.FOR_EXTERNAL_SHARE, - action.event.eventId ?: "", - action.messageContent.body, - action.messageContent.getFileUrl(), - action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + action.uploadEvent.eventId, + action.uploadEvent.contentWithAttachmentContent.body, + action.uploadEvent.contentWithAttachmentContent.getFileUrl(), + action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), it) } - _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.messageContent.body)) + _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) } catch (failure: Throwable) { _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt index 31c4e937c8..bed3b264cd 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt @@ -19,16 +19,16 @@ package im.vector.riotx.features.roomprofile.uploads import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.riotx.features.roomprofile.RoomProfileArgs data class RoomUploadsViewState( val roomId: String = "", val roomSummary: Async = Uninitialized, // Store cumul of pagination result, grouped by type - val mediaEvents: List = emptyList(), - val fileEvents: List = emptyList(), + val mediaEvents: List = emptyList(), + val fileEvents: List = emptyList(), // Current pagination request val asyncEventsRequest: Async = Uninitialized, // True if more result are available server side diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 2798cdc683..63f9e5215e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -20,8 +20,7 @@ import android.os.Bundle import android.view.View import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent +import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -54,8 +53,9 @@ class RoomUploadsFilesFragment @Inject constructor( controller.listener = null } - override fun onOpenClicked(event: Event) { - TODO() + override fun onOpenClicked(uploadEvent: UploadEvent) { + // Same action than Share + uploadsViewModel.handle(RoomUploadsAction.Share(uploadEvent)) } override fun onRetry() { @@ -66,12 +66,12 @@ class RoomUploadsFilesFragment @Inject constructor( uploadsViewModel.handle(RoomUploadsAction.LoadMore) } - override fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) { - uploadsViewModel.handle(RoomUploadsAction.Download(event, messageWithAttachmentContent)) + override fun onDownloadClicked(uploadEvent: UploadEvent) { + uploadsViewModel.handle(RoomUploadsAction.Download(uploadEvent)) } - override fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) { - uploadsViewModel.handle(RoomUploadsAction.Share(event, messageWithAttachmentContent)) + override fun onShareClicked(uploadEvent: UploadEvent) { + uploadsViewModel.handle(RoomUploadsAction.Share(uploadEvent)) } override fun invalidate() = withState(uploadsViewModel) { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index 0ad173be88..918fc433c9 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -21,10 +21,7 @@ import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading 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.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent +import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.riotx.R import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.errorWithRetryItem @@ -44,9 +41,9 @@ class UploadsFileController @Inject constructor( interface Listener { fun onRetry() fun loadMore() - fun onOpenClicked(event: Event) - fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) - fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) + fun onOpenClicked(uploadEvent: UploadEvent) + fun onDownloadClicked(uploadEvent: UploadEvent) + fun onShareClicked(uploadEvent: UploadEvent) } var listener: Listener? = null @@ -106,27 +103,25 @@ class UploadsFileController @Inject constructor( } } - private fun buildFileItems(fileEvents: List) { - fileEvents.forEach { - val messageContent = it.getClearContent()?.toModel() ?: return@forEach - val messageWithAttachmentContent = (messageContent as? MessageWithAttachmentContent) ?: return@forEach - + private fun buildFileItems(fileEvents: List) { + fileEvents.forEach { uploadEvent -> uploadsFileItem { - id(it.eventId ?: "") - title(messageWithAttachmentContent.body) - // TODO Resolve user displayName - subtitle(stringProvider.getString(R.string.uploads_files_subtitle, it.senderId, dateFormatter.formatRelativeDateTime(it.originServerTs))) + id(uploadEvent.eventId) + title(uploadEvent.contentWithAttachmentContent.body) + subtitle(stringProvider.getString(R.string.uploads_files_subtitle, + uploadEvent.uploadSenderInfo.getDisambiguatedDisplayName(), + dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs))) listener(object : UploadsFileItem.Listener { override fun onItemClicked() { - listener?.onOpenClicked(it) + listener?.onOpenClicked(uploadEvent) } override fun onDownloadClicked() { - listener?.onDownloadClicked(it, messageWithAttachmentContent) + listener?.onDownloadClicked(uploadEvent) } override fun onShareClicked() { - listener?.onShareClicked(it, messageWithAttachmentContent) + listener?.onShareClicked(uploadEvent) } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt index c6e3666fb5..1d797bec78 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -22,13 +22,11 @@ import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.isImageMessage -import im.vector.matrix.android.api.session.events.model.isVideoMessage -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.model.message.getFileUrl +import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.riotx.R import im.vector.riotx.core.epoxy.errorWithRetryItem @@ -115,13 +113,13 @@ class UploadsMediaController @Inject constructor( } } - private fun buildMediaItems(mediaEvents: List) { - mediaEvents.forEach { event -> - when { - event.isImageMessage() -> { - val data = event.toImageContentRendererData() ?: return@forEach + private fun buildMediaItems(mediaEvents: List) { + mediaEvents.forEach { uploadEvent -> + when (uploadEvent.contentWithAttachmentContent.msgType) { + MessageType.MSGTYPE_IMAGE -> { + val data = uploadEvent.toImageContentRendererData() ?: return@forEach uploadsImageItem { - id(event.eventId ?: "") + id(uploadEvent.eventId) imageContentRenderer(imageContentRenderer) data(data) listener(object : UploadsImageItem.Listener { @@ -131,10 +129,10 @@ class UploadsMediaController @Inject constructor( }) } } - event.isVideoMessage() -> { - val data = event.toVideoContentRendererData() ?: return@forEach + MessageType.MSGTYPE_VIDEO -> { + val data = uploadEvent.toVideoContentRendererData() ?: return@forEach uploadsVideoItem { - id(event.eventId ?: "") + id(uploadEvent.eventId) imageContentRenderer(imageContentRenderer) data(data) listener(object : UploadsVideoItem.Listener { @@ -148,11 +146,11 @@ class UploadsMediaController @Inject constructor( } } - private fun Event.toImageContentRendererData(): ImageContentRenderer.Data? { - val messageContent = getClearContent()?.toModel() ?: return null + private fun UploadEvent.toImageContentRendererData(): ImageContentRenderer.Data? { + val messageContent = (contentWithAttachmentContent as? MessageImageContent) ?: return null return ImageContentRenderer.Data( - eventId = eventId ?: "", + eventId = eventId, filename = messageContent.body, url = messageContent.getFileUrl(), elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), @@ -163,11 +161,11 @@ class UploadsMediaController @Inject constructor( ) } - private fun Event.toVideoContentRendererData(): VideoContentRenderer.Data? { - val messageContent = getClearContent()?.toModel() ?: return null + private fun UploadEvent.toVideoContentRendererData(): VideoContentRenderer.Data? { + val messageContent = (contentWithAttachmentContent as? MessageVideoContent) ?: return null val thumbnailData = ImageContentRenderer.Data( - eventId = eventId ?: "", + eventId = eventId, filename = messageContent.body, url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl, elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), @@ -178,7 +176,7 @@ class UploadsMediaController @Inject constructor( ) return VideoContentRenderer.Data( - eventId = eventId ?: "", + eventId = eventId, filename = messageContent.body, url = messageContent.getFileUrl(), elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), From 2adafbeb034c4611d31b47efeabe9ddf4eacf3d3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 12:19:30 +0200 Subject: [PATCH 52/59] Uploads: use SenderInfo in TimelineEvent --- .../SenderInfo.kt} | 20 +++++++++-------- .../session/room/timeline/TimelineEvent.kt | 13 ++--------- .../api/session/room/uploads/UploadEvent.kt | 3 ++- .../matrix/android/api/util/MatrixItem.kt | 3 +++ .../database/mapper/TimelineEventMapper.kt | 11 ++++++---- .../room/send/LocalEchoEventFactory.kt | 2 +- .../session/room/uploads/GetUploadsTask.kt | 14 ++++++------ .../home/room/detail/RoomDetailFragment.kt | 8 ++----- .../factory/MergedHeaderItemFactory.kt | 12 ++++------ .../format/DisplayableEventFormatter.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 22 +++++++++---------- .../helper/MessageInformationDataFactory.kt | 10 ++++----- .../reactions/ViewReactionsViewModel.kt | 2 +- .../notifications/NotifiableEventResolver.kt | 6 ++--- .../uploads/files/UploadsFileController.kt | 2 +- 15 files changed, 60 insertions(+), 70 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/{uploads/UploadSenderInfo.kt => sender/SenderInfo.kt} (63%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt similarity index 63% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt index 0fa4b2cb68..c3f0df8312 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadSenderInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt @@ -14,20 +14,22 @@ * limitations under the License. */ -package im.vector.matrix.android.api.session.room.uploads +package im.vector.matrix.android.api.session.room.sender -// TODO Maybe use this model for TimelineEvent as well -data class UploadSenderInfo( - val senderId: String, - val senderName: String?, +data class SenderInfo( + val userId: String, + /** + * Consider using [getDisambiguatedDisplayName] + */ + val displayName: String?, val isUniqueDisplayName: Boolean, - val senderAvatar: String? + val avatarUrl: String? ) { fun getDisambiguatedDisplayName(): String { return when { - senderName.isNullOrBlank() -> senderId - isUniqueDisplayName -> senderName - else -> "$senderName (${senderId})" + displayName.isNullOrBlank() -> userId + isUniqueDisplayName -> displayName + else -> "$displayName (${userId})" } } } 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 7adc438e20..273ea2366a 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 @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent import im.vector.matrix.android.api.session.room.model.message.isReply +import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent @@ -39,9 +40,7 @@ data class TimelineEvent( val localId: Long, val eventId: String, val displayIndex: Int, - val senderName: String?, - val isUniqueDisplayName: Boolean, - val senderAvatar: String?, + val senderInfo: SenderInfo, val annotations: EventAnnotationsSummary? = null, val readReceipts: List = emptyList() ) { @@ -69,14 +68,6 @@ data class TimelineEvent( } } - fun getDisambiguatedDisplayName(): String { - return when { - senderName.isNullOrBlank() -> root.senderId ?: "" - isUniqueDisplayName -> senderName - else -> "$senderName (${root.senderId})" - } - } - /** * Get the metadata associated with a key. * @param key the key to get the metadata diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt index 93ce59a233..5df2b9c9e0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/UploadEvent.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.uploads import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent +import im.vector.matrix.android.api.session.room.sender.SenderInfo /** * Wrapper around on Event. @@ -27,5 +28,5 @@ data class UploadEvent( val root: Event, val eventId: String, val contentWithAttachmentContent: MessageWithAttachmentContent, - val uploadSenderInfo: UploadSenderInfo + val senderInfo: SenderInfo ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt index d5aa897c7d..95c0435f1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.session.user.model.User import java.util.Locale @@ -154,3 +155,5 @@ fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlia fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) + +fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, getDisambiguatedDisplayName(), avatarUrl) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 4bd9b9855b..6653c1a448 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.ReadReceipt - +import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.TimelineEventEntity import javax.inject.Inject @@ -41,9 +41,12 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS annotations = timelineEventEntity.annotations?.asDomain(), localId = timelineEventEntity.localId, displayIndex = timelineEventEntity.displayIndex, - senderName = timelineEventEntity.senderName, - isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, - senderAvatar = timelineEventEntity.senderAvatar, + senderInfo = SenderInfo( + userId = timelineEventEntity.root?.sender ?: "", + displayName = timelineEventEntity.senderName, + isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, + avatarUrl = timelineEventEntity.senderAvatar + ), readReceipts = readReceipts ?.distinctBy { it.user diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 2a24094b5d..2d1e2775c0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -202,7 +202,7 @@ internal class LocalEchoEventFactory @Inject constructor( permalink, stringProvider.getString(R.string.message_reply_to_prefix), userLink, - originalEvent.getDisambiguatedDisplayName(), + originalEvent.senderInfo.getDisambiguatedDisplayName(), body.takeFormatted(), createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt index f6fd2a442b..cc78a02c88 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -20,9 +20,9 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent +import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult import im.vector.matrix.android.api.session.room.uploads.UploadEvent -import im.vector.matrix.android.api.session.room.uploads.UploadSenderInfo import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.filter.FilterFactory import im.vector.matrix.android.internal.session.room.RoomAPI @@ -60,7 +60,7 @@ internal class DefaultGetUploadsTask @Inject constructor( var uploadEvents = listOf() - val cacheOfSenderInfos = mutableMapOf() + val cacheOfSenderInfos = mutableMapOf() // Get a snapshot of all room members monarchy.doWithRealm { realm -> @@ -74,11 +74,11 @@ internal class DefaultGetUploadsTask @Inject constructor( val senderInfo = cacheOfSenderInfos.getOrPut(senderId) { val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(senderId) - UploadSenderInfo( - senderId = senderId, - senderName = roomMemberSummaryEntity?.displayName, + SenderInfo( + userId = senderId, + displayName = roomMemberSummaryEntity?.displayName, isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName), - senderAvatar = roomMemberSummaryEntity?.avatarUrl + avatarUrl = roomMemberSummaryEntity?.avatarUrl ) } @@ -86,7 +86,7 @@ internal class DefaultGetUploadsTask @Inject constructor( root = event, eventId = eventId, contentWithAttachmentContent = messageWithAttachmentContent, - uploadSenderInfo = senderInfo + senderInfo = senderInfo ) } } 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 bd9a4c9d7e..9dcf0d42ea 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 @@ -458,7 +458,7 @@ class RoomDetailFragment @Inject constructor( autoCompleter.enterSpecialMode() // switch to expanded bar composerLayout.composerRelatedMessageTitle.apply { - text = event.getDisambiguatedDisplayName() + text = event.senderInfo.getDisambiguatedDisplayName() setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId))) } @@ -477,11 +477,7 @@ class RoomDetailFragment @Inject constructor( composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) composerLayout.sendButton.contentDescription = getString(descriptionRes) - avatarRenderer.render( - MatrixItem.UserItem(event.root.senderId - ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), - composerLayout.composerRelatedMessageAvatar - ) + avatarRenderer.render(event.senderInfo.toMatrixItem(), composerLayout.composerRelatedMessageAvatar) composerLayout.expand { if (isAdded) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 9529693e6b..70d70c2765 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -85,12 +85,10 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { highlighted = true } - val senderAvatar = mergedEvent.senderAvatar - val senderName = mergedEvent.getDisambiguatedDisplayName() val data = BasedMergedItem.Data( userId = mergedEvent.root.senderId ?: "", - avatarUrl = senderAvatar, - memberName = senderName, + avatarUrl = mergedEvent.senderInfo.avatarUrl, + memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(), localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "" ) @@ -158,12 +156,10 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { highlighted = true } - val senderAvatar = mergedEvent.senderAvatar - val senderName = mergedEvent.getDisambiguatedDisplayName() val data = BasedMergedItem.Data( userId = mergedEvent.root.senderId ?: "", - avatarUrl = senderAvatar, - memberName = senderName, + avatarUrl = mergedEvent.senderInfo.avatarUrl, + memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(), localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "" ) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 2f7b52de62..ac587a8250 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -45,7 +45,7 @@ class DisplayableEventFormatter @Inject constructor( return stringProvider.getString(R.string.encrypted_message) } - val senderName = timelineEvent.getDisambiguatedDisplayName() + val senderName = timelineEvent.senderInfo.getDisambiguatedDisplayName() when (timelineEvent.root.getClearType()) { EventType.MESSAGE -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index f29bd72e0a..99fa25e76f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -47,20 +47,20 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root) - EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.senderInfo.getDisambiguatedDisplayName()) EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) EventType.MESSAGE, EventType.REACTION, EventType.KEY_VERIFICATION_START, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 695da73f89..92374edea7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -64,16 +64,14 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val showInformation = addDaySeparator - || event.senderAvatar != nextEvent?.senderAvatar - || event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName() + || event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl + || event.senderInfo.getDisambiguatedDisplayName() != nextEvent?.senderInfo?.getDisambiguatedDisplayName() || (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo || isTileTypeMessage(nextEvent) val time = dateFormatter.formatMessageHour(date) - val avatarUrl = event.senderAvatar - val memberName = event.getDisambiguatedDisplayName() - val formattedMemberName = span(memberName) { + val formattedMemberName = span(event.senderInfo.getDisambiguatedDisplayName()) { textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId)) } @@ -85,7 +83,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses sendState = event.root.sendState, time = time, ageLocalTS = event.root.ageLocalTs, - avatarUrl = avatarUrl, + avatarUrl = event.senderInfo.avatarUrl, memberName = formattedMemberName, showInformation = showInformation, orderedReactionList = event.annotations?.reactionsSummary diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 05cdbc0fd8..25e407eeb1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -111,7 +111,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted event.root.eventId!!, summary.key, event.root.senderId ?: "", - event.getDisambiguatedDisplayName(), + event.senderInfo.getDisambiguatedDisplayName(), dateFormatter.formatRelativeDateTime(event.root.originServerTs) ) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index 1f9f54127b..9401c69ad1 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -93,7 +93,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St // Ok room is not known in store, but we can still display something val body = displayableEventFormatter.format(event, false) val roomName = stringProvider.getString(R.string.notification_unknown_room_name) - val senderDisplayName = event.getDisambiguatedDisplayName() + val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName() val notifiableEvent = NotifiableMessageEvent( eventId = event.root.eventId!!, @@ -126,7 +126,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St val body = displayableEventFormatter.format(event, false).toString() val roomName = room.roomSummary()?.displayName ?: "" - val senderDisplayName = event.getDisambiguatedDisplayName() + val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName() val notifiableEvent = NotifiableMessageEvent( eventId = event.root.eventId!!, @@ -151,7 +151,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St ContentUrlResolver.ThumbnailMethod.SCALE) notifiableEvent.senderAvatarPath = session.contentUrlResolver() - .resolveThumbnail(event.senderAvatar, + .resolveThumbnail(event.senderInfo.avatarUrl, 250, 250, ContentUrlResolver.ThumbnailMethod.SCALE) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index 918fc433c9..8c4dd1f300 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -109,7 +109,7 @@ class UploadsFileController @Inject constructor( id(uploadEvent.eventId) title(uploadEvent.contentWithAttachmentContent.body) subtitle(stringProvider.getString(R.string.uploads_files_subtitle, - uploadEvent.uploadSenderInfo.getDisambiguatedDisplayName(), + uploadEvent.senderInfo.getDisambiguatedDisplayName(), dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs))) listener(object : UploadsFileItem.Listener { override fun onItemClicked() { From a95102a78f7a59e5604524f2fdadc5dbf0de6a76 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 13:24:42 +0200 Subject: [PATCH 53/59] Uploads: Use StateView for better Loading/Empty rendering --- .../vector/riotx/core/platform/StateView.kt | 29 +++------- .../uploads/files/RoomUploadsFilesFragment.kt | 49 ++++++++++++++--- .../uploads/files/UploadsFileController.kt | 55 +++---------------- .../uploads/media/RoomUploadsMediaFragment.kt | 53 ++++++++++++++---- .../uploads/media/UploadsMediaController.kt | 54 +++--------------- .../fragment_generic_state_view_recycler.xml | 13 +++++ vector/src/main/res/layout/view_state.xml | 1 + vector/src/main/res/values/strings.xml | 4 +- 8 files changed, 125 insertions(+), 133 deletions(-) create mode 100644 vector/src/main/res/layout/fragment_generic_state_view_recycler.xml diff --git a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt index 4c5a987b4b..bc24874f9f 100755 --- a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt @@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View import android.widget.FrameLayout +import androidx.core.view.isVisible import im.vector.riotx.R import kotlinx.android.synthetic.main.view_state.view.* @@ -31,6 +32,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? object Content : State() object Loading : State() data class Empty(val title: CharSequence? = null, val image: Drawable? = null, val message: CharSequence? = null) : State() + data class Error(val message: CharSequence? = null) : State() } @@ -59,34 +61,21 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? } private fun update(newState: State) { + progressBar.isVisible = newState is State.Loading + errorView.isVisible = newState is State.Error + emptyView.isVisible = newState is State.Empty + contentView?.isVisible = newState is State.Content + when (newState) { - is State.Content -> { - progressBar.visibility = View.INVISIBLE - errorView.visibility = View.INVISIBLE - emptyView.visibility = View.INVISIBLE - contentView?.visibility = View.VISIBLE - } - is State.Loading -> { - progressBar.visibility = View.VISIBLE - errorView.visibility = View.INVISIBLE - emptyView.visibility = View.INVISIBLE - contentView?.visibility = View.INVISIBLE - } + is State.Content -> Unit + is State.Loading -> Unit is State.Empty -> { - progressBar.visibility = View.INVISIBLE - errorView.visibility = View.INVISIBLE - emptyView.visibility = View.VISIBLE emptyImageView.setImageDrawable(newState.image) emptyMessageView.text = newState.message emptyTitleView.text = newState.title - contentView?.visibility = View.INVISIBLE } is State.Error -> { - progressBar.visibility = View.INVISIBLE - errorView.visibility = View.VISIBLE - emptyView.visibility = View.INVISIBLE errorMessageView.text = newState.message - contentView?.visibility = View.INVISIBLE } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 63f9e5215e..bba7a40440 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -18,6 +18,10 @@ package im.vector.riotx.features.roomprofile.uploads.files import android.os.Bundle import android.view.View +import androidx.core.content.ContextCompat +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.room.uploads.UploadEvent @@ -25,31 +29,37 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.trackItemsVisibilityChange +import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel -import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import kotlinx.android.synthetic.main.fragment_generic_state_view_recycler.* import javax.inject.Inject class RoomUploadsFilesFragment @Inject constructor( private val controller: UploadsFileController -) : VectorBaseFragment(), UploadsFileController.Listener { +) : VectorBaseFragment(), + UploadsFileController.Listener, + StateView.EventCallback { private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) - override fun getLayoutResId() = R.layout.fragment_generic_recycler + override fun getLayoutResId() = R.layout.fragment_generic_state_view_recycler override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.trackItemsVisibilityChange() - recyclerView.configureWith(controller, showDivider = true) + genericStateViewListStateView.contentView = genericStateViewListRecycler + genericStateViewListStateView.eventCallback = this + + genericStateViewListRecycler.trackItemsVisibilityChange() + genericStateViewListRecycler.configureWith(controller, showDivider = true) controller.listener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericStateViewListRecycler.cleanup() controller.listener = null } @@ -58,7 +68,7 @@ class RoomUploadsFilesFragment @Inject constructor( uploadsViewModel.handle(RoomUploadsAction.Share(uploadEvent)) } - override fun onRetry() { + override fun onRetryClicked() { uploadsViewModel.handle(RoomUploadsAction.Retry) } @@ -75,6 +85,29 @@ class RoomUploadsFilesFragment @Inject constructor( } override fun invalidate() = withState(uploadsViewModel) { state -> - controller.setData(state) + if (state.fileEvents.isEmpty()) { + when (state.asyncEventsRequest) { + is Loading -> { + genericStateViewListStateView.state = StateView.State.Loading + } + is Fail -> { + genericStateViewListStateView.state = StateView.State.Error(errorFormatter.toHumanReadable(state.asyncEventsRequest.error)) + } + is Success -> { + if (state.hasMore) { + // We need to load more items + loadMore() + } else { + genericStateViewListStateView.state = StateView.State.Empty( + title = getString(R.string.uploads_files_no_result), + image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_file) + ) + } + } + } + } else { + genericStateViewListStateView.state = StateView.State.Content + controller.setData(state) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index 8c4dd1f300..2b355af8a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -18,28 +18,20 @@ package im.vector.riotx.features.roomprofile.uploads.files import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.VisibilityState -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.riotx.R import im.vector.riotx.core.date.VectorDateFormatter -import im.vector.riotx.core.epoxy.errorWithRetryItem import im.vector.riotx.core.epoxy.loadingItem -import im.vector.riotx.core.epoxy.noResultItem -import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState import javax.inject.Inject class UploadsFileController @Inject constructor( - private val errorFormatter: ErrorFormatter, private val stringProvider: StringProvider, private val dateFormatter: VectorDateFormatter ) : TypedEpoxyController() { interface Listener { - fun onRetry() fun loadMore() fun onOpenClicked(uploadEvent: UploadEvent) fun onDownloadClicked(uploadEvent: UploadEvent) @@ -57,46 +49,15 @@ class UploadsFileController @Inject constructor( override fun buildModels(data: RoomUploadsViewState?) { data ?: return - if (data.fileEvents.isEmpty()) { - when (data.asyncEventsRequest) { - is Loading -> { - loadingItem { - id("loading") - } - } - is Fail -> { - errorWithRetryItem { - id("error") - text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error)) - listener { listener?.onRetry() } - } - } - is Success -> { - if (data.hasMore) { - // We need to load more items - listener?.loadMore() - loadingItem { - id("loading") - } - } else { - noResultItem { - id("noResult") - text(stringProvider.getString(R.string.uploads_files_no_result)) - } - } - } - } - } else { - buildFileItems(data.fileEvents) + buildFileItems(data.fileEvents) - if (data.hasMore) { - loadingItem { - // Always use a different id, because we can be notified several times of visibility state changed - id("loadMore${idx++}") - onVisibilityStateChanged { _, _, visibilityState -> - if (visibilityState == VisibilityState.VISIBLE) { - listener?.loadMore() - } + if (data.hasMore) { + loadingItem { + // Always use a different id, because we can be notified several times of visibility state changed + id("loadMore${idx++}") + onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + listener?.loadMore() } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index a722c8281f..a4e6c61238 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -19,37 +19,47 @@ package im.vector.riotx.features.roomprofile.uploads.media import android.os.Bundle import android.util.DisplayMetrics import android.view.View +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.trackItemsVisibilityChange +import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel -import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import kotlinx.android.synthetic.main.fragment_generic_state_view_recycler.* import javax.inject.Inject class RoomUploadsMediaFragment @Inject constructor( private val controller: UploadsMediaController, private val dimensionConverter: DimensionConverter -) : VectorBaseFragment(), UploadsMediaController.Listener { +) : VectorBaseFragment(), + UploadsMediaController.Listener, + StateView.EventCallback { private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) - override fun getLayoutResId() = R.layout.fragment_generic_recycler + override fun getLayoutResId() = R.layout.fragment_generic_state_view_recycler override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.trackItemsVisibilityChange() - recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns()) - recyclerView.adapter = controller.adapter - recyclerView.setHasFixedSize(true) + genericStateViewListStateView.contentView = genericStateViewListRecycler + genericStateViewListStateView.eventCallback = this + + genericStateViewListRecycler.trackItemsVisibilityChange() + genericStateViewListRecycler.layoutManager = GridLayoutManager(context, getNumberOfColumns()) + genericStateViewListRecycler.adapter = controller.adapter + genericStateViewListRecycler.setHasFixedSize(true) controller.listener = this } @@ -62,7 +72,7 @@ class RoomUploadsMediaFragment @Inject constructor( override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericStateViewListRecycler.cleanup() controller.listener = null } @@ -78,11 +88,34 @@ class RoomUploadsMediaFragment @Inject constructor( uploadsViewModel.handle(RoomUploadsAction.LoadMore) } - override fun onRetry() { + override fun onRetryClicked() { uploadsViewModel.handle(RoomUploadsAction.Retry) } override fun invalidate() = withState(uploadsViewModel) { state -> - controller.setData(state) + if (state.mediaEvents.isEmpty()) { + when (state.asyncEventsRequest) { + is Loading -> { + genericStateViewListStateView.state = StateView.State.Loading + } + is Fail -> { + genericStateViewListStateView.state = StateView.State.Error(errorFormatter.toHumanReadable(state.asyncEventsRequest.error)) + } + is Success -> { + if (state.hasMore) { + // We need to load more items + loadMore() + } else { + genericStateViewListStateView.state = StateView.State.Empty( + title = getString(R.string.uploads_media_no_result), + image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_image) + ) + } + } + } + } else { + genericStateViewListStateView.state = StateView.State.Content + controller.setData(state) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt index 1d797bec78..cd3e401dc5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -19,18 +19,12 @@ package im.vector.riotx.features.roomprofile.uploads.media import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.VisibilityState -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt -import im.vector.riotx.R -import im.vector.riotx.core.epoxy.errorWithRetryItem -import im.vector.riotx.core.epoxy.noResultItem import im.vector.riotx.core.epoxy.squareLoadingItem import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.resources.StringProvider @@ -48,7 +42,6 @@ class UploadsMediaController @Inject constructor( ) : TypedEpoxyController() { interface Listener { - fun onRetry() fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) fun loadMore() @@ -67,46 +60,15 @@ class UploadsMediaController @Inject constructor( override fun buildModels(data: RoomUploadsViewState?) { data ?: return - if (data.mediaEvents.isEmpty()) { - when (data.asyncEventsRequest) { - is Loading -> { - squareLoadingItem { - id("loading") - } - } - is Fail -> { - errorWithRetryItem { - id("error") - text(errorFormatter.toHumanReadable(data.asyncEventsRequest.error)) - listener { listener?.onRetry() } - } - } - is Success -> { - if (data.hasMore) { - // We need to load more items - listener?.loadMore() - squareLoadingItem { - id("loading") - } - } else { - noResultItem { - id("noResult") - text(stringProvider.getString(R.string.uploads_media_no_result)) - } - } - } - } - } else { - buildMediaItems(data.mediaEvents) + buildMediaItems(data.mediaEvents) - if (data.hasMore) { - squareLoadingItem { - // Always use a different id, because we can be notified several times of visibility state changed - id("loadMore${idx++}") - onVisibilityStateChanged { _, _, visibilityState -> - if (visibilityState == VisibilityState.VISIBLE) { - listener?.loadMore() - } + if (data.hasMore) { + squareLoadingItem { + // Always use a different id, because we can be notified several times of visibility state changed + id("loadMore${idx++}") + onVisibilityStateChanged { _, _, visibilityState -> + if (visibilityState == VisibilityState.VISIBLE) { + listener?.loadMore() } } } diff --git a/vector/src/main/res/layout/fragment_generic_state_view_recycler.xml b/vector/src/main/res/layout/fragment_generic_state_view_recycler.xml new file mode 100644 index 0000000000..410373b97f --- /dev/null +++ b/vector/src/main/res/layout/fragment_generic_state_view_recycler.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index c17e1b216b..082a0bb24c 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -73,6 +73,7 @@ android:layout_width="64dp" android:layout_height="64dp" android:layout_gravity="center_horizontal" + android:tint="?riotx_text_primary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b0ddce5ed3..36532f25e3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1789,11 +1789,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Couldn\'t handle share data MEDIA - There is no media in this room + There are no media in this room FILES %1$s at %2$s - There is no files in this room + There are no files in this room "It's spam" "It's inappropriate" From f0f3e8ddb9e5ba46f6eda056e62a5a3a2a02d97a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 14:27:10 +0200 Subject: [PATCH 54/59] Uploads: auto-review --- .../api/session/room/sender/SenderInfo.kt | 9 ++++---- .../session/room/uploads/GetUploadsResult.kt | 3 +-- .../matrix/android/api/util/MatrixItem.kt | 2 +- .../membership/RoomDisplayNameResolver.kt | 2 +- .../room/send/LocalEchoEventFactory.kt | 2 +- .../session/room/uploads/GetUploadsTask.kt | 3 +-- vector/build.gradle | 3 ++- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../factory/MergedHeaderItemFactory.kt | 4 ++-- .../format/DisplayableEventFormatter.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 22 +++++++++---------- .../helper/MessageInformationDataFactory.kt | 4 ++-- .../reactions/ViewReactionsViewModel.kt | 2 +- .../notifications/NotifiableEventResolver.kt | 4 ++-- .../uploads/RoomUploadsFragment.kt | 8 ------- .../uploads/RoomUploadsViewModel.kt | 9 ++++---- .../uploads/RoomUploadsViewState.kt | 1 - .../uploads/files/UploadsFileController.kt | 2 +- 18 files changed, 36 insertions(+), 48 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt index c3f0df8312..1a0908a6d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/sender/SenderInfo.kt @@ -19,17 +19,16 @@ package im.vector.matrix.android.api.session.room.sender data class SenderInfo( val userId: String, /** - * Consider using [getDisambiguatedDisplayName] + * Consider using [disambiguatedDisplayName] */ val displayName: String?, val isUniqueDisplayName: Boolean, val avatarUrl: String? ) { - fun getDisambiguatedDisplayName(): String { - return when { + val disambiguatedDisplayName: String + get() = when { displayName.isNullOrBlank() -> userId isUniqueDisplayName -> displayName - else -> "$displayName (${userId})" + else -> "$displayName ($userId)" } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt index bbe8641edd..4c75d909aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/uploads/GetUploadsResult.kt @@ -18,10 +18,9 @@ package im.vector.matrix.android.api.session.room.uploads data class GetUploadsResult( // List of fetched Events, most recent first - val events: List, + val uploadEvents: List, // token to get more events val nextToken: String, // True if there are more event to load val hasMore: Boolean ) - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt index 95c0435f1b..f30494711b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt @@ -156,4 +156,4 @@ fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAl fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) -fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, getDisambiguatedDisplayName(), avatarUrl) +fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) 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 e7a68b3b5b..3c1df50b75 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 @@ -126,7 +126,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: return name ?: roomId } - /** See [im.vector.matrix.android.api.session.room.timeline.TimelineEvent.getDisambiguatedDisplayName] */ + /** See [im.vector.matrix.android.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?, roomMemberHelper: RoomMemberHelper): String? { if (roomMemberSummary == null) return null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 2d1e2775c0..82d393e79a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -202,7 +202,7 @@ internal class LocalEchoEventFactory @Inject constructor( permalink, stringProvider.getString(R.string.message_reply_to_prefix), userLink, - originalEvent.senderInfo.getDisambiguatedDisplayName(), + originalEvent.senderInfo.disambiguatedDisplayName, body.takeFormatted(), createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt index cc78a02c88..fa707c0bf8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/uploads/GetUploadsTask.kt @@ -91,9 +91,8 @@ internal class DefaultGetUploadsTask @Inject constructor( } } - return GetUploadsResult( - events = uploadEvents, + uploadEvents = uploadEvents, nextToken = chunk.end ?: "", hasMore = chunk.hasMore() ) diff --git a/vector/build.gradle b/vector/build.gradle index b9a7fbcdc8..6f1afc5038 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -260,6 +260,7 @@ dependencies { def autofill_version = "1.0.0" def work_version = '2.3.3' def arch_version = '2.1.0' + def lifecycle_version = '2.2.0' implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") @@ -282,7 +283,7 @@ dependencies { implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0" implementation "com.squareup.moshi:moshi-adapters:$moshi_version" - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" // Log 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 9dcf0d42ea..f042cdcefb 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 @@ -458,7 +458,7 @@ class RoomDetailFragment @Inject constructor( autoCompleter.enterSpecialMode() // switch to expanded bar composerLayout.composerRelatedMessageTitle.apply { - text = event.senderInfo.getDisambiguatedDisplayName() + text = event.senderInfo.disambiguatedDisplayName setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId))) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 70d70c2765..419fd673d1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -88,7 +88,7 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av val data = BasedMergedItem.Data( userId = mergedEvent.root.senderId ?: "", avatarUrl = mergedEvent.senderInfo.avatarUrl, - memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(), + memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "" ) @@ -159,7 +159,7 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av val data = BasedMergedItem.Data( userId = mergedEvent.root.senderId ?: "", avatarUrl = mergedEvent.senderInfo.avatarUrl, - memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(), + memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, eventId = mergedEvent.root.eventId ?: "" ) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index ac587a8250..9ab48ad5ee 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -45,7 +45,7 @@ class DisplayableEventFormatter @Inject constructor( return stringProvider.getString(R.string.encrypted_message) } - val senderName = timelineEvent.senderInfo.getDisambiguatedDisplayName() + val senderName = timelineEvent.senderInfo.disambiguatedDisplayName when (timelineEvent.root.getClearType()) { EventType.MESSAGE -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 99fa25e76f..86c9f0ab5b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -47,20 +47,20 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root) - EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.senderInfo.disambiguatedDisplayName) EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName()) + EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.MESSAGE, EventType.REACTION, EventType.KEY_VERIFICATION_START, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 92374edea7..9a912b5af3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -65,13 +65,13 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val showInformation = addDaySeparator || event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl - || event.senderInfo.getDisambiguatedDisplayName() != nextEvent?.senderInfo?.getDisambiguatedDisplayName() + || event.senderInfo.disambiguatedDisplayName != nextEvent?.senderInfo?.disambiguatedDisplayName || (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo || isTileTypeMessage(nextEvent) val time = dateFormatter.formatMessageHour(date) - val formattedMemberName = span(event.senderInfo.getDisambiguatedDisplayName()) { + val formattedMemberName = span(event.senderInfo.disambiguatedDisplayName) { textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId)) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 25e407eeb1..3d8382ab98 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -111,7 +111,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted event.root.eventId!!, summary.key, event.root.senderId ?: "", - event.senderInfo.getDisambiguatedDisplayName(), + event.senderInfo.disambiguatedDisplayName, dateFormatter.formatRelativeDateTime(event.root.originServerTs) ) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index 9401c69ad1..a2dc8d33f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -93,7 +93,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St // Ok room is not known in store, but we can still display something val body = displayableEventFormatter.format(event, false) val roomName = stringProvider.getString(R.string.notification_unknown_room_name) - val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName() + val senderDisplayName = event.senderInfo.disambiguatedDisplayName val notifiableEvent = NotifiableMessageEvent( eventId = event.root.eventId!!, @@ -126,7 +126,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St val body = displayableEventFormatter.format(event, false).toString() val roomName = room.roomSummary()?.displayName ?: "" - val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName() + val senderDisplayName = event.senderInfo.disambiguatedDisplayName val notifiableEvent = NotifiableMessageEvent( eventId = event.root.eventId!!, diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt index 9ef2688b17..cf20cca834 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -87,14 +87,6 @@ class RoomUploadsFragment @Inject constructor( } } - /* - override fun onDestroyView() { - super.onDestroyView() - // Clear your view, unsubscribe... - } - - */ - override fun invalidate() = withState(viewModel) { state -> renderRoomSummary(state) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt index dcb0963ef0..952e80c035 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -96,7 +96,7 @@ class RoomUploadsViewModel @AssistedInject constructor( token = result.nextToken - val groupedEvents = result.events + val groupedUploadEvents = result.uploadEvents .groupBy { it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_IMAGE || it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_VIDEO @@ -105,13 +105,13 @@ class RoomUploadsViewModel @AssistedInject constructor( setState { copy( asyncEventsRequest = Success(Unit), - mediaEvents = this.mediaEvents + groupedEvents[true].orEmpty(), - fileEvents = this.fileEvents + groupedEvents[false].orEmpty(), + mediaEvents = this.mediaEvents + groupedUploadEvents[true].orEmpty(), + fileEvents = this.fileEvents + groupedUploadEvents[false].orEmpty(), hasMore = result.hasMore ) } } catch (failure: Throwable) { - // TODO Post fail + _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) setState { copy( asyncEventsRequest = Fail(failure) @@ -163,7 +163,6 @@ class RoomUploadsViewModel @AssistedInject constructor( action.uploadEvent.contentWithAttachmentContent.getFileUrl(), action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), it) - } _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt index bed3b264cd..3e31a3cdd6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsViewState.kt @@ -37,4 +37,3 @@ data class RoomUploadsViewState( constructor(args: RoomProfileArgs) : this(roomId = args.roomId) } - diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt index 2b355af8a9..60f966e7d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/files/UploadsFileController.kt @@ -70,7 +70,7 @@ class UploadsFileController @Inject constructor( id(uploadEvent.eventId) title(uploadEvent.contentWithAttachmentContent.body) subtitle(stringProvider.getString(R.string.uploads_files_subtitle, - uploadEvent.senderInfo.getDisambiguatedDisplayName(), + uploadEvent.senderInfo.disambiguatedDisplayName, dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs))) listener(object : UploadsFileItem.Listener { override fun onItemClicked() { From c52aae7c2986d479d2e0003cf7c141b9c962577f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 14:49:01 +0200 Subject: [PATCH 55/59] Uploads: Snackbar --- .../features/roomprofile/uploads/RoomUploadsFragment.kt | 6 +++--- vector/src/main/res/layout/fragment_room_uploads.xml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt index cf20cca834..99aeb4231b 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -18,11 +18,11 @@ package im.vector.riotx.features.roomprofile.uploads import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.core.net.toUri import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R @@ -77,9 +77,9 @@ class RoomUploadsFragment @Inject constructor( mediaMimeType = getMimeTypeFromUri(requireContext(), it.file.toUri()) ) if (saved) { - Toast.makeText(requireContext(), R.string.media_file_added_to_gallery, Toast.LENGTH_LONG).show() + Snackbar.make(roomUploadsCoordinator, R.string.media_file_added_to_gallery, Snackbar.LENGTH_LONG).show() } else { - Toast.makeText(requireContext(), R.string.error_adding_media_file_to_gallery, Toast.LENGTH_LONG).show() + Snackbar.make(roomUploadsCoordinator, R.string.error_adding_media_file_to_gallery, Snackbar.LENGTH_LONG).show() } } is RoomUploadsViewEvents.Failure -> showFailure(it.throwable) diff --git a/vector/src/main/res/layout/fragment_room_uploads.xml b/vector/src/main/res/layout/fragment_room_uploads.xml index 36d779b533..5e289d4724 100644 --- a/vector/src/main/res/layout/fragment_room_uploads.xml +++ b/vector/src/main/res/layout/fragment_room_uploads.xml @@ -2,6 +2,7 @@ From 7a3dbecc081da2971079ef07787ed1da3adb097f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 May 2020 14:52:28 +0200 Subject: [PATCH 56/59] Fixes #860 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index d88f9952e4..2dc0cb3ea0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Changes in RiotX 0.21.0 (2020-XX-XX) Features ✨: - Identity server support (#607) - Switch language support (#41) + - Display list of attachments of a room (#860) Improvements 🙌: - Better connectivity lost indicator when airplane mode is on From 7ae52d676d716714be5a42153df60dc16517f111 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 23:50:10 +0200 Subject: [PATCH 57/59] Use directly java.net.proxy class --- .../im/vector/matrix/android/api/Matrix.kt | 10 +++++--- .../android/api/config/ProxyConfiguration.kt | 24 ------------------- .../android/internal/di/NetworkModule.kt | 6 ++--- 3 files changed, 9 insertions(+), 31 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 67415e5cdc..912103ff95 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -23,7 +23,6 @@ import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.AuthenticationService -import im.vector.matrix.android.api.config.ProxyConfiguration import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt @@ -33,14 +32,19 @@ import im.vector.matrix.android.internal.network.UserAgentHolder import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager import java.io.InputStream -import java.util.concurrent.atomic.AtomicBoolean +import java.net.Proxy import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject data class MatrixConfiguration( val applicationFlavor: String = "Default-application-flavor", val cryptoConfig: MXCryptoConfig = MXCryptoConfig(), - val proxyConfig: ProxyConfiguration? = null + /** + * Optional proxy to connect to the matrix servers + * You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port) + */ + val proxy: Proxy? = null ) { interface Provider { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt deleted file mode 100644 index b23ffa82f9..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/config/ProxyConfiguration.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.api.config - -import java.net.Proxy - -/** - * This is the configuration to use a proxy to connect to the matrix servers - */ -data class ProxyConfiguration(val hostname: String, val port: Int, val proxyType: Proxy.Type) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt index 559d80c68f..ddde4fc7d6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt @@ -29,8 +29,6 @@ import im.vector.matrix.android.internal.network.interceptors.FormattedJsonHttpL import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okreplay.OkReplayInterceptor -import java.net.InetSocketAddress -import java.net.Proxy import java.util.concurrent.TimeUnit @Module @@ -86,8 +84,8 @@ internal object NetworkModule { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) } - matrixConfiguration.proxyConfig?.let { - proxy(Proxy(it.proxyType, InetSocketAddress(it.hostname, it.port))) + matrixConfiguration.proxy?.let { + proxy(it) } } .addInterceptor(okReplayInterceptor) From e379ccf0864ab71ab21037d617cfa690a1dd0eb9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 23:53:36 +0200 Subject: [PATCH 58/59] Extract MatrixConfiguration to its own file, for a better visibility --- .../im/vector/matrix/android/api/Matrix.kt | 17 --------- .../matrix/android/api/MatrixConfiguration.kt | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixConfiguration.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 912103ff95..1a4c4aceee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -23,7 +23,6 @@ import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.AuthenticationService -import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments @@ -32,26 +31,10 @@ import im.vector.matrix.android.internal.network.UserAgentHolder import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager import java.io.InputStream -import java.net.Proxy import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -data class MatrixConfiguration( - val applicationFlavor: String = "Default-application-flavor", - val cryptoConfig: MXCryptoConfig = MXCryptoConfig(), - /** - * Optional proxy to connect to the matrix servers - * You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port) - */ - val proxy: Proxy? = null -) { - - interface Provider { - fun providesMatrixConfiguration(): MatrixConfiguration - } -} - /** * This is the main entry point to the matrix sdk. * To get the singleton instance, use getInstance static method. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixConfiguration.kt new file mode 100644 index 0000000000..d7c62f8bef --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixConfiguration.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api + +import im.vector.matrix.android.api.crypto.MXCryptoConfig +import java.net.Proxy + +data class MatrixConfiguration( + val applicationFlavor: String = "Default-application-flavor", + val cryptoConfig: MXCryptoConfig = MXCryptoConfig(), + /** + * Optional proxy to connect to the matrix servers + * You can create one using for instance Proxy(proxyType, InetSocketAddress(hostname, port) + */ + val proxy: Proxy? = null +) { + + /** + * Can be implemented by your Application class + */ + interface Provider { + fun providesMatrixConfiguration(): MatrixConfiguration + } +} From 0509e76f183976030da423757bbf0ea53a5d697c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 May 2020 23:54:53 +0200 Subject: [PATCH 59/59] var -> val --- .../im/vector/matrix/android/api/crypto/MXCryptoConfig.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt index a8d576bae9..fa8178334c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt @@ -19,11 +19,11 @@ package im.vector.matrix.android.api.crypto /** * Class to define the parameters used to customize or configure the end-to-end crypto. */ -data class MXCryptoConfig( +data class MXCryptoConfig constructor( // Tell whether the encryption of the event content is enabled for the invited members. // SDK clients can disable this by settings it to false. // Note that the encryption for the invited members will be blocked if the history visibility is "joined". - var enableEncryptionForInvitedMembers: Boolean = true, + val enableEncryptionForInvitedMembers: Boolean = true, /** * If set to true, the SDK will automatically ignore room key request (gossiping) @@ -31,6 +31,5 @@ data class MXCryptoConfig( * If set to false, the request will be forwarded to the application layer; in this * case the application can decide to prompt the user. */ - var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true - + val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true )