From 49b7726ac866ccc7add1c516c8865eb3847dcb14 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 14 Feb 2022 15:09:01 +0200 Subject: [PATCH 0001/1430] - Integrate /relations API to create a live thread timeline --- .../session/room/timeline/ChunkEntityTest.kt | 24 +- .../sdk/api/session/events/model/Event.kt | 6 +- .../session/room/timeline/TimelineEvent.kt | 1 + .../database/RealmSessionStoreMigration.kt | 14 +- .../database/helper/ChunkEntityHelper.kt | 9 +- .../database/helper/ThreadEventsHelper.kt | 2 + .../database/mapper/TimelineEventMapper.kt | 1 + .../internal/database/model/ChunkEntity.kt | 22 +- .../database/model/TimelineEventEntity.kt | 3 + .../database/query/ChunkEntityQueries.kt | 14 +- .../EventAnnotationsSummaryEntityQuery.kt | 2 +- .../EventRelationsAggregationProcessor.kt | 10 +- .../sdk/internal/session/room/RoomAPI.kt | 2 + .../room/relation/DefaultRelationService.kt | 8 +- .../threads/FetchThreadTimelineTask.kt | 208 ++++++++++++------ .../session/room/timeline/DefaultTimeline.kt | 4 + .../room/timeline/DefaultTimelineService.kt | 5 +- .../room/timeline/LoadTimelineStrategy.kt | 58 ++++- .../session/room/timeline/TimelineChunk.kt | 45 +++- .../room/timeline/TokenChunkEventPersistor.kt | 24 +- .../sync/handler/room/RoomSyncHandler.kt | 35 ++- .../list/viewmodel/ThreadListController.kt | 2 +- 22 files changed, 395 insertions(+), 104 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt index 69ae57e644..5c011c8b2f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt @@ -62,7 +62,11 @@ internal class ChunkEntityTest : InstrumentedTest { val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { realm.copyToRealm(it) } - chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) + chunk.addTimelineEvent( + roomId = ROOM_ID, + eventEntity = fakeEvent, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = emptyMap()) chunk.timelineEvents.size shouldBeEqualTo 1 } } @@ -74,8 +78,16 @@ internal class ChunkEntityTest : InstrumentedTest { val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let { realm.copyToRealm(it) } - chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) - chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap()) + chunk.addTimelineEvent( + roomId = ROOM_ID, + eventEntity = fakeEvent, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = emptyMap()) + chunk.addTimelineEvent( + roomId = ROOM_ID, + eventEntity = fakeEvent, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = emptyMap()) chunk.timelineEvents.size shouldBeEqualTo 1 } } @@ -144,7 +156,11 @@ internal class ChunkEntityTest : InstrumentedTest { val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let { realm.copyToRealm(it) } - addTimelineEvent(roomId, fakeEvent, direction, emptyMap()) + addTimelineEvent( + roomId = roomId, + eventEntity = fakeEvent, + direction = direction, + roomMemberContentsByUser = emptyMap()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index df57ca5681..ed9a057375 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -201,7 +201,11 @@ data class Event( */ fun getDecryptedTextSummary(): String? { if (isRedacted()) return "Message Deleted" - val text = getDecryptedValue() ?: return null + val text = getDecryptedValue() ?: run { + if (isPoll()) {return getPollQuestion() ?: "created a poll."} + return null + } + return when { isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) isFileMessage() -> "sent a file." diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 6f8bae876b..c03d0fd17b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -54,6 +54,7 @@ data class TimelineEvent( * It's not unique on the timeline as it's reset on each chunk. */ val displayIndex: Int, + var ownedByThreadChunk: Boolean = false, val senderInfo: SenderInfo, val annotations: EventAnnotationsSummary? = null, val readReceipts: List = emptyList() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index dfb0915566..056c4b0ceb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -57,7 +57,7 @@ internal class RealmSessionStoreMigration @Inject constructor( ) : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 24L + const val SESSION_STORE_SCHEMA_VERSION = 26L } /** @@ -94,6 +94,7 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion <= 21) migrateTo22(realm) if (oldVersion <= 22) migrateTo23(realm) if (oldVersion <= 23) migrateTo24(realm) + if (oldVersion <= 24) migrateTo25(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -489,4 +490,15 @@ internal class RealmSessionStoreMigration @Inject constructor( ?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java) ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true) } + + private fun migrateTo25(realm: DynamicRealm){ + Timber.d("Step 24 -> 25") + realm.schema.get("ChunkEntity") + ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + ?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED) + + realm.schema.get("TimelineEventEntity") + ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java) + + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 289db9fa15..007017510c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -82,17 +82,18 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity, internal fun ChunkEntity.addTimelineEvent(roomId: String, eventEntity: EventEntity, direction: PaginationDirection, - roomMemberContentsByUser: Map? = null) { + ownedByThreadChunk: Boolean = false, + roomMemberContentsByUser: Map? = null): TimelineEventEntity? { val eventId = eventEntity.eventId if (timelineEvents.find(eventId) != null) { - return + return null } val displayIndex = nextDisplayIndex(direction) val localId = TimelineEventEntity.nextId(realm) val senderId = eventEntity.sender ?: "" // Update RR for the sender of a new message with a dummy one - val readReceiptsSummaryEntity = handleReadReceipts(realm, roomId, eventEntity, senderId) + val readReceiptsSummaryEntity = if (!ownedByThreadChunk) handleReadReceipts(realm, roomId, eventEntity, senderId) else null val timelineEventEntity = realm.createObject().apply { this.localId = localId this.root = eventEntity @@ -102,6 +103,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, ?.also { it.cleanUp(eventEntity.sender) } this.readReceipts = readReceiptsSummaryEntity this.displayIndex = displayIndex + this.ownedByThreadChunk = ownedByThreadChunk val roomMemberContent = roomMemberContentsByUser?.get(senderId) this.senderAvatar = roomMemberContent?.avatarUrl this.senderName = roomMemberContent?.displayName @@ -113,6 +115,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, } // numberOfTimelineEvents++ timelineEvents.add(timelineEventEntity) + return timelineEventEntity } private fun computeIsUnique( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index f703bfaf82..7f6b64da75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -98,6 +98,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: val messages = TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) .count() .toInt() @@ -156,6 +157,7 @@ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) + .equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) .sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt index f3bea68c26..1020fa33da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt @@ -46,6 +46,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, avatarUrl = timelineEventEntity.senderAvatar ), + ownedByThreadChunk = timelineEventEntity.ownedByThreadChunk, readReceipts = readReceipts ?.distinctBy { it.user diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index c45c27ed08..8e4d6fc916 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -23,6 +23,7 @@ import io.realm.annotations.Index import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.extensions.assertIsManaged import org.matrix.android.sdk.internal.extensions.clearWith +import timber.log.Timber internal open class ChunkEntity(@Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null @@ -33,7 +34,10 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, var timelineEvents: RealmList = RealmList(), // Only one chunk will have isLastForward == true @Index var isLastForward: Boolean = false, - @Index var isLastBackward: Boolean = false + @Index var isLastBackward: Boolean = false, + // Threads + @Index var rootThreadEventId: String? = null, + @Index var isLastForwardThread: Boolean = false, ) : RealmObject() { fun identifier() = "${prevToken}_$nextToken" @@ -58,3 +62,19 @@ internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRo } deleteFromRealm() } + +/** + * Delete the chunk along with the thread events that were temporarily created + */ +internal fun ChunkEntity.deleteAndClearThreadEvents() { + assertIsManaged() + timelineEvents + .filter { it.ownedByThreadChunk } + .forEach { + it.deleteOnCascade(false) + } + deleteFromRealm() +} + + + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt index 185f0e2dcc..aacd6570bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt @@ -32,6 +32,9 @@ internal open class TimelineEventEntity(var localId: Long = 0, var isUniqueDisplayName: Boolean = false, var senderAvatar: String? = null, var senderMembershipEventId: String? = null, + // ownedByThreadChunk indicates that the current TimelineEventEntity belongs + // to a thread chunk and is a temporarily event. + var ownedByThreadChunk: Boolean = false, var readReceipts: ReadReceiptsSummaryEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index 156a8dd767..ece46555a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -45,10 +45,22 @@ internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, room .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .findFirst() } - +internal fun ChunkEntity.Companion.findLastForwardChunkOfThread(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? { + return where(realm, roomId) + .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) + .findFirst() +} +internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId: String, event: String): ChunkEntity? { + return where(realm, roomId) + .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, arrayListOf(event).toTypedArray()) + .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) + .findFirst() +} internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where() .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray()) + .isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) .findAll() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index 14cb7e22da..6caa832110 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -34,7 +34,7 @@ internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId this.roomId = roomId } // Denormalization - TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findAll()?.forEach { it.annotations = obj } return obj diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index acceaf6e24..2eebb70bdc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -57,6 +57,7 @@ import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryE import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where @@ -117,8 +118,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() ?.let { - TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findFirst() - ?.let { tet -> tet.annotations = it } + TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() + ?.forEach { tet -> tet.annotations = it } } } @@ -335,7 +336,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } if (!isLocalEcho) { - val replaceEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() + val replaceEvent = TimelineEventEntity + .where(realm, roomId, eventId) + .equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) + .findFirst() handleThreadSummaryEdition(editedEvent, replaceEvent, existingSummary?.editions) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 399bfbd0e4..86929e013f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -227,6 +227,8 @@ internal interface RoomAPI { @Path("eventId") eventId: String, @Path("relationType") relationType: String, @Path("eventType") eventType: String, + @Query("from") from: String? = null, + @Query("to") to: String? = null, @Query("limit") limit: Int? = null ): RelationsResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 3abf28fdd4..d22583e8b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -206,7 +206,13 @@ internal class DefaultRelationService @AssistedInject constructor( } override suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean { - return fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(roomId, rootThreadEventId)) + fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( + roomId, + rootThreadEventId, + null, + 10 + )) + return true } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index e0d501c515..6b071fbd6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Matrix.org Foundation C.I.C. + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent -import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity @@ -36,8 +35,10 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore -import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom +import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where @@ -47,16 +48,39 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject -internal interface FetchThreadTimelineTask : Task { +/*** + * This class is responsible to Fetch paginated chunks of the thread timeline using the /relations API + * + * + * How it works + * + * The problem? + * - We cannot use the existing timeline architecture to paginate through the timeline + * - We want our new events to be live, so any interactions with them like reactions will continue to work. We should + * handle appropriately the existing events from /messages api with the new events from /relations. + * - Handling edge cases like receiving an event from /messages while you have already created a new one from the /relations response + * + * The solution + * We generate a temporarily thread chunk that will be used to store any new paginated results from the /relations api + * We bind the timeline events from that chunk with the already existing ones. So we will have one common instance, and + * all reactions, edits etc will continue to work. If the events do not exists we create them + * and we will reuse the same EventEntity instance when (and if) the same event will be fetched from the main (/messages) timeline + * + */ +internal interface FetchThreadTimelineTask : Task { data class Params( val roomId: String, - val rootThreadEventId: String + val rootThreadEventId: String, + val from: String?, + val limit: Int + ) } @@ -69,93 +93,133 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( private val cryptoService: DefaultCryptoService ) : FetchThreadTimelineTask { - override suspend fun execute(params: FetchThreadTimelineTask.Params): Boolean { + enum class Result { + SHOULD_FETCH_MORE, + REACHED_END, + SUCCESS + } + + override suspend fun execute(params: FetchThreadTimelineTask.Params): Result { val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId) val response = executeRequest(globalErrorReceiver) { roomAPI.getRelations( roomId = params.roomId, eventId = params.rootThreadEventId, relationType = RelationType.IO_THREAD, + from = params.from, eventType = if (isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE, - limit = 2000 + limit = params.limit ) } - val threadList = response.chunks + listOfNotNull(response.originalEvent) + Timber.i("###THREADS FetchThreadTimelineTask Fetched size:${response.chunks.size} nextBatch:${response.nextBatch} ") + return handleRelationsResponse(response, params) + } - return storeNewEventsIfNeeded(threadList, params.roomId) + private suspend fun handleRelationsResponse(response: RelationsResponse, + params: FetchThreadTimelineTask.Params): Result { + + val threadList = response.chunks + val threadRootEvent = response.originalEvent + val hasReachEnd = response.nextBatch == null + + monarchy.awaitTransaction { realm -> + + val threadChunk = ChunkEntity.findLastForwardChunkOfThread(realm, params.roomId, params.rootThreadEventId) + ?: run { + return@awaitTransaction + } + + threadChunk.prevToken = response.nextBatch + val roomMemberContentsByUser = HashMap() + + for (event in threadList) { + if (event.eventId == null || event.senderId == null || event.type == null) { + continue + } + + if (threadChunk.timelineEvents.find(event.eventId) != null) { + // Event already exists in thread chunk, skip it + Timber.i("###THREADS FetchThreadTimelineTask event: ${event.eventId} already exists in thread chunk, skip it") + continue + } + + val timelineEvent = TimelineEventEntity + .where(realm, roomId = params.roomId, event.eventId) + .findFirst() + + if (timelineEvent != null) { + // Event already exists but not in the thread chunk + // Lets added there + Timber.i("###THREADS FetchThreadTimelineTask event: ${event.eventId} exists but not in the thread chunk, add it at the end") + threadChunk.timelineEvents.add(timelineEvent) + } else { + Timber.i("###THREADS FetchThreadTimelineTask event: ${event.eventId} is brand NEW create an entity and add it!") + val eventEntity = createEventEntity(params.roomId, event, realm) + roomMemberContentsByUser.addSenderState(realm, params.roomId, event.senderId) + threadChunk.addTimelineEvent( + roomId = params.roomId, + eventEntity = eventEntity, + direction = PaginationDirection.FORWARDS, + ownedByThreadChunk = true, + roomMemberContentsByUser = roomMemberContentsByUser) + } + } + + if (hasReachEnd) { + val rootThread = TimelineEventEntity + .where(realm, roomId = params.roomId, params.rootThreadEventId) + .findFirst() + if (rootThread != null) { + // If root thread event already exists add it to our chunk + threadChunk.timelineEvents.add(rootThread) + Timber.i("###THREADS FetchThreadTimelineTask root thread event: ${params.rootThreadEventId} found and added!") + } else if (threadRootEvent?.senderId != null) { + // Case when thread event is not in the device + Timber.i("###THREADS FetchThreadTimelineTask root thread event: ${params.rootThreadEventId} NOT FOUND! Lets create a temp one") + val eventEntity = createEventEntity(params.roomId, threadRootEvent, realm) + roomMemberContentsByUser.addSenderState(realm, params.roomId, threadRootEvent.senderId) + threadChunk.addTimelineEvent( + roomId = params.roomId, + eventEntity = eventEntity, + direction = PaginationDirection.FORWARDS, + ownedByThreadChunk = true, + roomMemberContentsByUser = roomMemberContentsByUser) + } + } + } + + return if (hasReachEnd) { + Result.REACHED_END + } else { + Result.SHOULD_FETCH_MORE + } + } + + // TODO Reuse this function to all the app + /** + * If we don't have any new state on this user, get it from db + */ + private fun HashMap.addSenderState(realm: Realm, roomId: String, senderId: String) { + getOrPut(senderId) { + CurrentStateEventEntity + .getOrNull(realm, roomId, senderId, EventType.STATE_ROOM_MEMBER) + ?.root?.asDomain() + ?.getFixedRoomMemberContent() + } } /** - * Store new events if they are not already received, and returns weather or not, - * a timeline update should be made - * @param threadList is the list containing the thread replies - * @param roomId the roomId of the the thread - * @return + * Create an EventEntity to be added in the TimelineEventEntity */ - private suspend fun storeNewEventsIfNeeded(threadList: List, roomId: String): Boolean { - var eventsSkipped = 0 - monarchy - .awaitTransaction { realm -> - val chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) - - val optimizedThreadSummaryMap = hashMapOf() - val roomMemberContentsByUser = HashMap() - - for (event in threadList.reversed()) { - if (event.eventId == null || event.senderId == null || event.type == null) { - eventsSkipped++ - continue - } - - if (EventEntity.where(realm, event.eventId).findFirst() != null) { - // Skip if event already exists - eventsSkipped++ - continue - } - if (event.isEncrypted()) { - // Decrypt events that will be stored - decryptIfNeeded(event, roomId) - } - - handleReaction(realm, event, roomId) - - val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) - - // Sender info - roomMemberContentsByUser.getOrPut(event.senderId) { - // If we don't have any new state on this user, get it from db - val rootStateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root - rootStateEvent?.asDomain()?.getFixedRoomMemberContent() - } - - chunk?.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) - eventEntity.rootThreadEventId?.let { - // This is a thread event - optimizedThreadSummaryMap[it] = eventEntity - } ?: run { - // This is a normal event or a root thread one - optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity - } - } - - optimizedThreadSummaryMap.updateThreadSummaryIfNeeded( - roomId = roomId, - realm = realm, - currentUserId = userId, - shouldUpdateNotifications = false - ) - } - Timber.i("----> size: ${threadList.size} | skipped: $eventsSkipped | threads: ${threadList.map { it.eventId }}") - - return eventsSkipped == threadList.size + private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { + val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } + return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) } /** * Invoke the event decryption mechanism for a specific event */ - private fun decryptIfNeeded(event: Event, roomId: String) { try { // Event from sync does not have roomId, so add it to the event first diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 3dd4225b2c..5662986663 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer @@ -58,6 +59,7 @@ internal class DefaultTimeline(private val roomId: String, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + fetchThreadTimelineTask: FetchThreadTimelineTask, timelineEventMapper: TimelineEventMapper, timelineInput: TimelineInput, threadsAwarenessHandler: ThreadsAwarenessHandler, @@ -89,7 +91,9 @@ internal class DefaultTimeline(private val roomId: String, realm = backgroundRealm, eventDecryptor = eventDecryptor, paginationTask = paginationTask, + realmConfiguration = realmConfiguration, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + fetchThreadTimelineTask = fetchThreadTimelineTask, getContextOfEventTask = getEventTask, timelineInput = timelineInput, timelineEventMapper = timelineEventMapper, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index d7d61f0b47..df552b6178 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.task.TaskExecutor @@ -55,6 +56,7 @@ internal class DefaultTimelineService @AssistedInject constructor( private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val timelineEventMapper: TimelineEventMapper, private val loadRoomMembersTask: LoadRoomMembersTask, private val threadsAwarenessHandler: ThreadsAwarenessHandler, @@ -76,10 +78,11 @@ internal class DefaultTimelineService @AssistedInject constructor( realmConfiguration = monarchy.realmConfiguration, coroutineDispatchers = coroutineDispatchers, paginationTask = paginationTask, + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineEventMapper = timelineEventMapper, timelineInput = timelineInput, eventDecryptor = eventDecryptor, - fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + fetchThreadTimelineTask = fetchThreadTimelineTask, loadRoomMembersTask = loadRoomMembersTask, readReceiptHandler = readReceiptHandler, getEventTask = contextOfEventTask, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index f332c4a35f..867589ccc0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -19,20 +19,28 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm +import io.realm.RealmConfiguration import io.realm.RealmResults +import io.realm.kotlin.createObject import kotlinx.coroutines.CompletableDeferred import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.deleteAndClearThreadEvents import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler +import timber.log.Timber import java.util.concurrent.atomic.AtomicReference /** @@ -76,6 +84,8 @@ internal class LoadTimelineStrategy( val realm: AtomicReference, val eventDecryptor: TimelineEventDecryptor, val paginationTask: PaginationTask, + val realmConfiguration: RealmConfiguration, + val fetchThreadTimelineTask: FetchThreadTimelineTask, val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, val getContextOfEventTask: GetContextOfEventTask, val timelineInput: TimelineInput, @@ -90,7 +100,6 @@ internal class LoadTimelineStrategy( private var getContextLatch: CompletableDeferred? = null private var chunkEntity: RealmResults? = null private var timelineChunk: TimelineChunk? = null - private val chunkEntityListener = OrderedRealmCollectionChangeListener { _: RealmResults, changeSet: OrderedCollectionChangeSet -> // Can be call either when you open a permalink on an unknown event // or when there is a gap in the timeline. @@ -170,6 +179,9 @@ internal class LoadTimelineStrategy( getContextLatch?.cancel() chunkEntity = null timelineChunk = null + if(mode is Mode.Thread) { + clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId) + } } suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult { @@ -185,6 +197,9 @@ internal class LoadTimelineStrategy( return LoadMoreResult.FAILURE } } + if (mode is Mode.Thread) { + return timelineChunk?.loadMoreThread(count, Timeline.Direction.BACKWARDS) ?: LoadMoreResult.FAILURE + } return timelineChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE } @@ -201,7 +216,7 @@ internal class LoadTimelineStrategy( } private fun buildSendingEvents(): List { - return if (hasReachedLastForward()) { + return if (hasReachedLastForward() || mode is Mode.Thread) { sendingEventsDataSource.buildSendingEvents() } else { emptyList() @@ -219,13 +234,48 @@ internal class LoadTimelineStrategy( ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId)) } is Mode.Thread -> { + recreateThreadChunkEntity(realm, mode.rootThreadEventId) ChunkEntity.where(realm, roomId) - .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) + .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, mode.rootThreadEventId) + .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) .findAll() } } } + /** + * Clear any existing thread chunk entity and create a new one, with the + * rootThreadEventId included + */ + private fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) { + realm.executeTransaction { + // Lets delete the chunk and start a new one + ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { + Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..") + } + val threadChunk = it.createObject().apply { + Timber.i("###THREADS LoadTimelineStrategy [onStart] Created new thread chunk with rootThreadEventId: $rootThreadEventId") + this.rootThreadEventId = rootThreadEventId + this.isLastForwardThread = true + } + if (threadChunk.isValid) { + RoomEntity.where(it, roomId).findFirst()?.addIfNecessary(threadChunk) + } + } + } + + /** + * Clear any existing thread chunk + */ + private fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) { + realm.executeTransaction { + ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { + Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..") + } + + } + } + private fun hasReachedLastForward(): Boolean { return timelineChunk?.hasReachedLastForward().orFalse() } @@ -237,8 +287,10 @@ internal class LoadTimelineStrategy( timelineSettings = dependencies.timelineSettings, roomId = roomId, timelineId = timelineId, + fetchThreadTimelineTask = dependencies.fetchThreadTimelineTask, eventDecryptor = dependencies.eventDecryptor, paginationTask = dependencies.paginationTask, + realmConfiguration = dependencies.realmConfiguration, fetchTokenAndPaginateTask = dependencies.fetchTokenAndPaginateTask, timelineEventMapper = dependencies.timelineEventMapper, uiEchoManager = uiEchoManager, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 8507b63d1f..7f6e5b6c7c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener +import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery import io.realm.RealmResults @@ -36,6 +37,8 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import timber.log.Timber import java.util.Collections @@ -50,8 +53,10 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, private val timelineSettings: TimelineSettings, private val roomId: String, private val timelineId: String, + private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val eventDecryptor: TimelineEventDecryptor, private val paginationTask: PaginationTask, + private val realmConfiguration: RealmConfiguration, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, private val uiEchoManager: UIEchoManager? = null, @@ -142,6 +147,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, val loadFromStorage = loadFromStorage(count, direction).also { logLoadedFromStorage(it, direction) } + if (loadFromStorage.numberOfEvents == 6) { + Timber.i("here") + } val offsetCount = count - loadFromStorage.numberOfEvents @@ -158,6 +166,29 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } + /** + * This function will fetch more live thread timeline events using the /relations api. It will + * always fetch results, while we want our data to be up to dated. + */ + suspend fun loadMoreThread(count: Int, direction: Timeline.Direction): LoadMoreResult { + + return if (direction == Timeline.Direction.BACKWARDS) { + try { + fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( + roomId, + timelineSettings.rootThreadEventId!!, + chunkEntity.prevToken, + count + )).toLoadMoreResult() + } catch (failure: Throwable) { + Timber.e(failure, "Failed to fetch thread timeline events from the server") + LoadMoreResult.FAILURE + } + } else { + LoadMoreResult.FAILURE + } + } + private suspend fun delegateLoadMore(fetchFromServerIfNeeded: Boolean, offsetCount: Int, direction: Timeline.Direction): LoadMoreResult { return if (direction == Timeline.Direction.FORWARDS) { val nextChunkEntity = chunkEntity.nextChunk @@ -287,7 +318,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, * @return the number of events loaded. If we are in a thread timeline it also returns * whether or not we reached the end/root message */ - private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage { + private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage { val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage() val baseQuery = timelineEventEntities.where() @@ -414,6 +445,14 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } + private fun DefaultFetchThreadTimelineTask.Result.toLoadMoreResult(): LoadMoreResult { + return when (this) { + DefaultFetchThreadTimelineTask.Result.REACHED_END -> LoadMoreResult.REACHED_END + DefaultFetchThreadTimelineTask.Result.SHOULD_FETCH_MORE, + DefaultFetchThreadTimelineTask.Result.SUCCESS -> LoadMoreResult.SUCCESS + } + } + private fun getOffsetIndex(): Int { var offset = 0 var currentNextChunk = nextChunk @@ -455,6 +494,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } } + if (insertions.isNotEmpty() || modifications.isNotEmpty()) { onBuiltEvents(true) } @@ -489,6 +529,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, timelineId = timelineId, eventDecryptor = eventDecryptor, paginationTask = paginationTask, + realmConfiguration = realmConfiguration, + fetchThreadTimelineTask = fetchThreadTimelineTask, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, timelineEventMapper = timelineEventMapper, uiEchoManager = uiEchoManager, @@ -535,7 +577,6 @@ private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmR .or() .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId) .endGroup() - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .findAll() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 6607e71bd9..63383a99b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find @@ -49,10 +50,10 @@ import javax.inject.Inject * Insert Chunk in DB, and eventually link next and previous chunk in db. */ internal class TokenChunkEventPersistor @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, - @UserId private val userId: String, - private val lightweightSettingsStorage: LightweightSettingsStorage, - private val liveEventManager: Lazy) { + @SessionDatabase private val monarchy: Monarchy, + @UserId private val userId: String, + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val liveEventManager: Lazy) { enum class Result { SHOULD_FETCH_MORE, @@ -145,9 +146,12 @@ internal class TokenChunkEventPersistor @Inject constructor( if (event.eventId == null || event.senderId == null) { return@forEach } - // We check for the timeline event with this id + // We check for the timeline event with this id, but not in the thread chunk val eventId = event.eventId - val existingTimelineEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() + val existingTimelineEvent = TimelineEventEntity + .where(realm, roomId, eventId) + .equalTo(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, false) + .findFirst() // If it exists, we want to stop here, just link the prevChunk val existingChunk = existingTimelineEvent?.chunk?.firstOrNull() if (existingChunk != null) { @@ -173,7 +177,7 @@ internal class TokenChunkEventPersistor @Inject constructor( return@processTimelineEvents } val ageLocalTs = event.unsignedData?.age?.let { now - it } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) + var eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) { event.prevContent @@ -183,7 +187,11 @@ internal class TokenChunkEventPersistor @Inject constructor( roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } liveEventManager.get().dispatchPaginatedEventReceived(event, roomId) - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) + currentChunk.addTimelineEvent( + roomId = roomId, + eventEntity = eventEntity, + direction = direction, + roomMemberContentsByUser = roomMemberContentsByUser) if (lightweightSettingsStorage.areThreadMessagesEnabled()) { eventEntity.rootThreadEventId?.let { // This is a thread event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 99e6521eb7..1aa0162354 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -46,10 +46,12 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where @@ -343,6 +345,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return roomEntity } + val customList = arrayListOf() private fun handleTimelineEvents(realm: Realm, roomId: String, roomEntity: RoomEntity, @@ -406,11 +409,18 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle rootStateEvent?.asDomain()?.getFixedRoomMemberContent() } - chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) + val timelineEventAdded = chunkEntity.addTimelineEvent( + roomId = roomId, + eventEntity = eventEntity, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = roomMemberContentsByUser) if (lightweightSettingsStorage.areThreadMessagesEnabled()) { eventEntity.rootThreadEventId?.let { // This is a thread event optimizedThreadSummaryMap[it] = eventEntity + // Add the same thread timeline event to Thread Chunk + addToThreadChunkIfNeeded(realm, roomId, it, timelineEventAdded, roomEntity) + } ?: run { // This is a normal event or a root thread one optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity @@ -455,6 +465,29 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return chunkEntity } + /** + * Adds new event to the appropriate thread chunk. If the event is already in + * the thread timeline and /relations api, we should not added it + */ + private fun addToThreadChunkIfNeeded(realm: Realm, + roomId: String, + threadId: String, + timelineEventEntity: TimelineEventEntity?, + roomEntity: RoomEntity) { + + val eventId = timelineEventEntity?.eventId ?: return + + ChunkEntity.findLastForwardChunkOfThread(realm, roomId, threadId)?.let { threadChunk -> + val existingEvent = threadChunk.timelineEvents.find(eventId) + if (existingEvent?.ownedByThreadChunk == true) { + Timber.i("###THREADS RoomSyncHandler event:${timelineEventEntity.eventId} already exists, do not add") + return@addToThreadChunkIfNeeded + } + threadChunk.timelineEvents.add(0, timelineEventEntity) + roomEntity.addIfNecessary(threadChunk) + } + } + private fun decryptIfNeeded(event: Event, roomId: String) { try { // Event from sync does not have roomId, so add it to the event first diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index 8bc6bd73e9..32cb006810 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -65,7 +65,7 @@ class ThreadListController @Inject constructor( id(timelineEvent.eventId) avatarRenderer(host.avatarRenderer) matrixItem(timelineEvent.senderInfo.toMatrixItem()) - title(timelineEvent.senderInfo.displayName) + title(timelineEvent.senderInfo.displayName.orEmpty()) date(date) rootMessageDeleted(timelineEvent.root.isRedacted()) threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE) From 83d937b842b28acd5f34a1c9120de812dbfaa12b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 14 Feb 2022 15:10:30 +0200 Subject: [PATCH 0002/1430] format ktlint --- .../org/matrix/android/sdk/api/session/events/model/Event.kt | 2 +- .../sdk/internal/database/RealmSessionStoreMigration.kt | 3 +-- .../matrix/android/sdk/internal/database/model/ChunkEntity.kt | 4 ---- .../session/room/relation/threads/FetchThreadTimelineTask.kt | 1 - .../internal/session/room/timeline/LoadTimelineStrategy.kt | 3 +-- .../sdk/internal/session/room/timeline/TimelineChunk.kt | 1 - .../sdk/internal/session/sync/handler/room/RoomSyncHandler.kt | 2 -- 7 files changed, 3 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index ed9a057375..97eee9188c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -202,7 +202,7 @@ data class Event( fun getDecryptedTextSummary(): String? { if (isRedacted()) return "Message Deleted" val text = getDecryptedValue() ?: run { - if (isPoll()) {return getPollQuestion() ?: "created a poll."} + if (isPoll()) { return getPollQuestion() ?: "created a poll." } return null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 056c4b0ceb..5706a4ca06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -491,7 +491,7 @@ internal class RealmSessionStoreMigration @Inject constructor( ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true) } - private fun migrateTo25(realm: DynamicRealm){ + private fun migrateTo25(realm: DynamicRealm) { Timber.d("Step 24 -> 25") realm.schema.get("ChunkEntity") ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) @@ -499,6 +499,5 @@ internal class RealmSessionStoreMigration @Inject constructor( realm.schema.get("TimelineEventEntity") ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java) - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 8e4d6fc916..ca8049fd96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -23,7 +23,6 @@ import io.realm.annotations.Index import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.extensions.assertIsManaged import org.matrix.android.sdk.internal.extensions.clearWith -import timber.log.Timber internal open class ChunkEntity(@Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null @@ -75,6 +74,3 @@ internal fun ChunkEntity.deleteAndClearThreadEvents() { } deleteFromRealm() } - - - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index 6b071fbd6e..e7b91ebab7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -118,7 +118,6 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( private suspend fun handleRelationsResponse(response: RelationsResponse, params: FetchThreadTimelineTask.Params): Result { - val threadList = response.chunks val threadRootEvent = response.originalEvent val hasReachEnd = response.nextBatch == null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index 867589ccc0..a26008369a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -179,7 +179,7 @@ internal class LoadTimelineStrategy( getContextLatch?.cancel() chunkEntity = null timelineChunk = null - if(mode is Mode.Thread) { + if (mode is Mode.Thread) { clearThreadChunkEntity(dependencies.realm.get(), mode.rootThreadEventId) } } @@ -272,7 +272,6 @@ internal class LoadTimelineStrategy( ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..") } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 7f6e5b6c7c..864b3f0dd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -171,7 +171,6 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, * always fetch results, while we want our data to be up to dated. */ suspend fun loadMoreThread(count: Int, direction: Timeline.Direction): LoadMoreResult { - return if (direction == Timeline.Direction.BACKWARDS) { try { fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 1aa0162354..573af7c696 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -420,7 +420,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle optimizedThreadSummaryMap[it] = eventEntity // Add the same thread timeline event to Thread Chunk addToThreadChunkIfNeeded(realm, roomId, it, timelineEventAdded, roomEntity) - } ?: run { // This is a normal event or a root thread one optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity @@ -474,7 +473,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle threadId: String, timelineEventEntity: TimelineEventEntity?, roomEntity: RoomEntity) { - val eventId = timelineEventEntity?.eventId ?: return ChunkEntity.findLastForwardChunkOfThread(realm, roomId, threadId)?.let { threadChunk -> From 27bc43c24c5d3ba56c09e7a05f1e3c2e3e5d9288 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 14 Feb 2022 15:33:51 +0200 Subject: [PATCH 0003/1430] Fix realm migration --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo025.kt | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index f4a8ae2c67..12e60da114 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -56,7 +57,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 24L + val schemaVersion = 25L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -85,5 +86,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 22) MigrateSessionTo022(realm).perform() if (oldVersion < 23) MigrateSessionTo023(realm).perform() if (oldVersion < 24) MigrateSessionTo024(realm).perform() + if (oldVersion < 25) MigrateSessionTo025(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt new file mode 100644 index 0000000000..2a859d8b5a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import io.realm.FieldAttribute +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 24) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("ChunkEntity") + ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + ?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED) + + realm.schema.get("TimelineEventEntity") + ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java) + } +} From e9e5d680a1a247d552c2a2869f7caa1ca69df3b4 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 14 Feb 2022 16:51:56 +0200 Subject: [PATCH 0004/1430] Fix realm migration from 25 to 26 --- .../database/RealmSessionStoreMigration.kt | 2 ++ .../database/migration/MigrateSessionTo026.kt | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 12e60da114..e84bdc2d30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -87,5 +88,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 23) MigrateSessionTo023(realm).perform() if (oldVersion < 24) MigrateSessionTo024(realm).perform() if (oldVersion < 25) MigrateSessionTo025(realm).perform() + if (oldVersion < 26) MigrateSessionTo026(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt new file mode 100644 index 0000000000..d499365bb3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import io.realm.FieldAttribute +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { + + override fun doMigrate(realm: DynamicRealm) { + + realm.schema.get("ChunkEntity") + ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + ?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED) + + realm.schema.get("TimelineEventEntity") + ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java) + } +} From 830c38f50b38a47fc8bae0229b9dd15a164d76e3 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 14 Feb 2022 16:53:29 +0200 Subject: [PATCH 0005/1430] format ktlint --- .../sdk/internal/database/migration/MigrateSessionTo026.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt index d499365bb3..597d6d1cbe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { override fun doMigrate(realm: DynamicRealm) { - realm.schema.get("ChunkEntity") ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) ?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED) From 83088bbe5ac8d6630d2b7657f6d486c432e46b9f Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 18 Feb 2022 17:21:10 +0200 Subject: [PATCH 0006/1430] Introduce live thread summaries using the enhanced /messages API from MSC 3440 Add capabilities to support local thread list to not supported servers --- .../org/matrix/android/sdk/flow/FlowRoom.kt | 8 +- .../events/model/AggregatedRelations.kt | 3 +- .../model/LatestThreadUnsignedRelation.kt | 30 ++ .../homeserver/HomeServerCapabilities.kt | 6 +- .../android/sdk/api/session/room/Room.kt | 2 + .../room/model/relation/RelationService.kt | 9 - .../session/room/threads/ThreadsService.kt | 50 ++- .../room/threads/local/ThreadsLocalService.kt | 68 ++++ .../room/threads/model/ThreadEditions.kt | 20 ++ .../room/threads/model/ThreadSummary.kt | 33 ++ .../threads/model/ThreadSummaryUpdateType.kt | 22 ++ .../matrix/android/sdk/api/util/MatrixItem.kt | 3 + .../database/RealmSessionStoreMigration.kt | 4 +- .../database/helper/ChunkEntityHelper.kt | 2 +- .../database/helper/RoomEntityHelper.kt | 7 + .../database/helper/ThreadEventsHelper.kt | 6 +- .../database/helper/ThreadSummaryHelper.kt | 332 ++++++++++++++++++ .../mapper/HomeServerCapabilitiesMapper.kt | 3 +- .../database/mapper/ThreadSummaryMapper.kt | 48 +++ .../database/migration/MigrateSessionTo027.kt | 49 +++ .../internal/database/model/ChunkEntity.kt | 7 +- .../internal/database/model/EventEntity.kt | 4 +- .../model/HomeServerCapabilitiesEntity.kt | 3 +- .../sdk/internal/database/model/RoomEntity.kt | 11 + .../database/model/SessionRealmModule.kt | 4 +- .../model/threads/ThreadSummaryEntity.kt | 43 +++ .../query/ThreadSummaryEntityQueries.kt | 59 ++++ .../internal/session/filter/FilterFactory.kt | 16 +- .../session/filter/RoomEventFilter.kt | 7 +- .../homeserver/GetCapabilitiesResult.kt | 8 +- .../GetHomeServerCapabilitiesTask.kt | 1 + .../sdk/internal/session/room/DefaultRoom.kt | 3 + .../EventRelationsAggregationProcessor.kt | 14 +- .../sdk/internal/session/room/RoomAPI.kt | 2 +- .../sdk/internal/session/room/RoomFactory.kt | 3 + .../sdk/internal/session/room/RoomModule.kt | 5 + .../room/relation/DefaultRelationService.kt | 12 - .../threads/FetchThreadSummariesTask.kt | 108 ++++++ .../threads/FetchThreadTimelineTask.kt | 1 - .../room/threads/DefaultThreadsService.kt | 75 ++-- .../local/DefaultThreadsLocalService.kt | 103 ++++++ .../sync/handler/room/RoomSyncHandler.kt | 18 +- .../home/room/threads/ThreadsActivity.kt | 14 +- .../list/viewmodel/ThreadListController.kt | 62 +++- .../list/viewmodel/ThreadListViewModel.kt | 41 ++- .../list/viewmodel/ThreadListViewState.kt | 3 +- .../threads/list/views/ThreadListFragment.kt | 28 +- 47 files changed, 1221 insertions(+), 139 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/LatestThreadUnsignedRelation.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index 826f584f6a..fb8bf2df27 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.send.UserDraft +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -101,13 +102,18 @@ class FlowRoom(private val room: Room) { return room.getLiveRoomNotificationState().asFlow() } + fun liveThreadSummaries(): Flow> { + return room.getAllThreadSummariesLive().asFlow() + .startWith(room.coroutineDispatchers.io) { + room.getAllThreadSummaries() + } + } fun liveThreadList(): Flow> { return room.getAllThreadsLive().asFlow() .startWith(room.coroutineDispatchers.io) { room.getAllThreads() } } - fun liveLocalUnreadThreadList(): Flow> { return room.getMarkedThreadNotificationsLive().asFlow() .startWith(room.coroutineDispatchers.io) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt index 34096d603f..7547d1cfe9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt @@ -49,5 +49,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class AggregatedRelations( @Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null, - @Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null + @Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null, + @Json(name = RelationType.IO_THREAD) val latestThread: LatestThreadUnsignedRelation? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/LatestThreadUnsignedRelation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/LatestThreadUnsignedRelation.kt new file mode 100644 index 0000000000..cc52dfc02c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/LatestThreadUnsignedRelation.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.api.session.events.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LatestThreadUnsignedRelation( + override val limited: Boolean? = false, + override val count: Int? = 0, + @Json(name = "latest_event") + val event: Event? = null, + @Json(name = "current_user_participated") + val isUserParticipating: Boolean? = false + +) : UnsignedRelationInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 2256dfb8f0..9db3876b74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -50,7 +50,11 @@ data class HomeServerCapabilities( * This capability describes the default and available room versions a server supports, and at what level of stability. * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. */ - val roomVersions: RoomVersionCapabilities? = null + val roomVersions: RoomVersionCapabilities? = null, + /** + * True if the home server support threading + */ + var canUseThreading: Boolean = false ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index d930a5d0fd..be65b883b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.threads.ThreadsService +import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService @@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional interface Room : TimelineService, ThreadsService, + ThreadsLocalService, SendService, DraftService, ReadService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 09114436f0..4409898908 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -163,13 +163,4 @@ interface RelationService { autoMarkdown: Boolean = false, formattedText: String? = null, eventReplied: TimelineEvent? = null): Cancelable? - - /** - * Get all the thread replies for the specified rootThreadEventId - * The return list will contain the original root thread event and all the thread replies to that event - * Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready - * from the backend - * @param rootThreadEventId the root thread eventId - */ - suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt index e4d1d979e1..99c0dc7d0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt @@ -17,51 +17,43 @@ package org.matrix.android.sdk.api.session.room.threads import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary /** - * This interface defines methods to interact with threads related features. - * It's implemented at the room level within the main timeline. + * This interface defines methods to interact with thread related features. + * It's the dynamic threads implementation and the homeserver must return + * a capability entry for threads. If the server do not support m.thread + * then [ThreadsLocalService] should be used instead */ interface ThreadsService { /** - * Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level + * Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level */ - fun getAllThreadsLive(): LiveData> + fun getAllThreadSummariesLive(): LiveData> /** - * Returns a list of all the thread root TimelineEvents that exists at the room level + * Returns a list of all the [ThreadSummary] that exists at the room level */ - fun getAllThreads(): List + fun getAllThreadSummaries(): List /** - * Returns a [LiveData] list of all the marked unread threads that exists at the room level - */ - fun getMarkedThreadNotificationsLive(): LiveData> - - /** - * Returns a list of all the marked unread threads that exists at the room level - */ - fun getMarkedThreadNotifications(): List - - /** - * Returns whether or not the current user is participating in the thread - * @param rootThreadEventId the eventId of the current thread - */ - fun isUserParticipatingInThread(rootThreadEventId: String): Boolean - - /** - * Enhance the provided root thread TimelineEvent [List] by adding the latest + * Enhance the provided ThreadSummary[List] by adding the latest * message edition for that thread * @return the enhanced [List] with edited updates */ - fun mapEventsWithEdition(threads: List): List + fun enhanceWithEditions(threads: List): List /** - * Marks the current thread as read in local DB. - * note: read receipts within threads are not yet supported with the API - * @param rootThreadEventId the root eventId of the current thread + * Fetch all thread replies for the specified thread using the /relations api + * @param rootThreadEventId the root thread eventId + * @param from defines the token that will fetch from that position + * @param limit defines the number of max results the api will respond with */ - suspend fun markThreadAsRead(rootThreadEventId: String) + suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int) + + /** + * Fetch all thread summaries for the current room using the enhanced /messages api + */ + suspend fun fetchThreadSummaries() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt new file mode 100644 index 0000000000..f7b379e382 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/local/ThreadsLocalService.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.threads.local + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +/** + * This interface defines methods to interact with thread related features. + * It's the local threads implementation and assumes that the homeserver + * do not support threads + */ +interface ThreadsLocalService { + + /** + * Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level + */ + fun getAllThreadsLive(): LiveData> + + /** + * Returns a list of all the thread root TimelineEvents that exists at the room level + */ + fun getAllThreads(): List + + /** + * Returns a [LiveData] list of all the marked unread threads that exists at the room level + */ + fun getMarkedThreadNotificationsLive(): LiveData> + + /** + * Returns a list of all the marked unread threads that exists at the room level + */ + fun getMarkedThreadNotifications(): List + + /** + * Returns whether or not the current user is participating in the thread + * @param rootThreadEventId the eventId of the current thread + */ + fun isUserParticipatingInThread(rootThreadEventId: String): Boolean + + /** + * Enhance the provided root thread TimelineEvent [List] by adding the latest + * message edition for that thread + * @return the enhanced [List] with edited updates + */ + fun mapEventsWithEdition(threads: List): List + + /** + * Marks the current thread as read in local DB. + * note: read receipts within threads are not yet supported with the API + * @param rootThreadEventId the root eventId of the current thread + */ + suspend fun markThreadAsRead(rootThreadEventId: String) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt new file mode 100644 index 0000000000..db92e800e4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.api.session.room.threads.model + +data class ThreadEditions(var rootThreadEdition: String? = null, + var latestThreadEdition: String? = null) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt new file mode 100644 index 0000000000..f26be85e85 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.api.session.room.threads.model + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.sender.SenderInfo + +/** + * The main thread Summary model, mainly used to display the thread list + */ +data class ThreadSummary(val roomId: String, + val rootEvent: Event?, + val latestEvent: Event?, + val rootEventId: String, + val rootThreadSenderInfo: SenderInfo, + val latestThreadSenderInfo: SenderInfo, + val isUserParticipating: Boolean, + val numberOfThreads: Int, + val threadEditions: ThreadEditions = ThreadEditions()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt new file mode 100644 index 0000000000..744265cb94 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 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 org.matrix.android.sdk.api.session.room.threads.model + +enum class ThreadSummaryUpdateType { + REPLACE, + ADD +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index 3396c4a6c9..17d7d96a38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.util import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -178,6 +179,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) +fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) } + fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) { MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index e84bdc2d30..24ac310653 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 25L + val schemaVersion = 27L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -89,5 +90,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 24) MigrateSessionTo024(realm).perform() if (oldVersion < 25) MigrateSessionTo025(realm).perform() if (oldVersion < 26) MigrateSessionTo026(realm).perform() + if (oldVersion < 27) MigrateSessionTo027(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 007017510c..d2e3e99b75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -118,7 +118,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, return timelineEventEntity } -private fun computeIsUnique( +fun computeIsUnique( realm: Realm, roomId: String, isLastForward: Boolean, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/RoomEntityHelper.kt index 724f307e3b..9ad2708b43 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/RoomEntityHelper.kt @@ -18,9 +18,16 @@ package org.matrix.android.sdk.internal.database.helper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity internal fun RoomEntity.addIfNecessary(chunkEntity: ChunkEntity) { if (!chunks.contains(chunkEntity)) { chunks.add(chunkEntity) } } + +internal fun RoomEntity.addIfNecessary(threadSummary: ThreadSummaryEntity) { + if (!threadSummaries.contains(threadSummary)) { + threadSummaries.add(threadSummary) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 7f6b64da75..ee3008d40b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId -private typealias ThreadSummary = Pair? +private typealias Summary = Pair? /** * Finds the root thread event and update it with the latest message summary along with the number @@ -93,7 +93,7 @@ internal fun EventEntity.markEventAsRoot( * @param rootThreadEventId The root eventId that will find the number of threads * @return A ThreadSummary containing the counted threads and the latest event message */ -internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): ThreadSummary { +internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { // Number of messages val messages = TimelineEventEntity .whereRoomId(realm, roomId = roomId) @@ -124,7 +124,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return ThreadSummary(messages, result) + return Summary(messages, result) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt new file mode 100644 index 0000000000..d19056adfa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -0,0 +1,332 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.helper + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.Sort +import io.realm.kotlin.createObject +import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType +import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor +import timber.log.Timber +import java.util.UUID + +internal fun ThreadSummaryEntity.updateThreadSummary( + rootThreadEventEntity: EventEntity, + numberOfThreads: Int?, + latestThreadEventEntity: EventEntity?, + isUserParticipating: Boolean, + roomMemberContentsByUser: HashMap) { + updateThreadSummaryRootEvent(rootThreadEventEntity, roomMemberContentsByUser) + updateThreadSummaryLatestEvent(latestThreadEventEntity, roomMemberContentsByUser) + + // Update latest event +// latestThreadEventEntity?.toTimelineEventEntity(roomMemberContentsByUser)?.let { +// Timber.i("###THREADS FetchThreadSummariesTask ThreadSummaryEntity updated latest event:${it.eventId} !") +// this.eventEntity?.threadSummaryLatestMessage = it +// } + + // Update number of threads + this.isUserParticipating = isUserParticipating + numberOfThreads?.let { + // Update only when there is an actual value + this.numberOfThreads = it + } +} + +/** + * Updates the root thread event properties + */ +internal fun ThreadSummaryEntity.updateThreadSummaryRootEvent( + rootThreadEventEntity: EventEntity, + roomMemberContentsByUser: HashMap +) { + val roomId = rootThreadEventEntity.roomId + val rootThreadRoomMemberContent = roomMemberContentsByUser[rootThreadEventEntity.sender ?: ""] + this.rootThreadEventEntity = rootThreadEventEntity + this.rootThreadSenderAvatar = rootThreadRoomMemberContent?.avatarUrl + this.rootThreadSenderName = rootThreadRoomMemberContent?.displayName + this.rootThreadIsUniqueDisplayName = if (rootThreadRoomMemberContent?.displayName != null) { + computeIsUnique(realm, roomId, false, rootThreadRoomMemberContent, roomMemberContentsByUser) + } else { + true + } +} + +/** + * Updates the latest thread event properties + */ +internal fun ThreadSummaryEntity.updateThreadSummaryLatestEvent( + latestThreadEventEntity: EventEntity?, + roomMemberContentsByUser: HashMap +) { + val roomId = latestThreadEventEntity?.roomId ?: return + val latestThreadRoomMemberContent = roomMemberContentsByUser[latestThreadEventEntity.sender ?: ""] + this.latestThreadEventEntity = latestThreadEventEntity + this.latestThreadSenderAvatar = latestThreadRoomMemberContent?.avatarUrl + this.latestThreadSenderName = latestThreadRoomMemberContent?.displayName + this.latestThreadIsUniqueDisplayName = if (latestThreadRoomMemberContent?.displayName != null) { + computeIsUnique(realm, roomId, false, latestThreadRoomMemberContent, roomMemberContentsByUser) + } else { + true + } +} + +private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap): TimelineEventEntity { + val roomId = roomId + val eventId = eventId + val localId = TimelineEventEntity.nextId(realm) + val senderId = sender ?: "" + + val timelineEventEntity = realm.createObject().apply { + this.localId = localId + this.root = this@toTimelineEventEntity + this.eventId = eventId + this.roomId = roomId + this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() + ?.also { it.cleanUp(sender) } + this.ownedByThreadChunk = true // To skip it from the original event flow + val roomMemberContent = roomMemberContentsByUser[senderId] + this.senderAvatar = roomMemberContent?.avatarUrl + this.senderName = roomMemberContent?.displayName + isUniqueDisplayName = if (roomMemberContent?.displayName != null) { + computeIsUnique(realm, roomId, false, roomMemberContent, roomMemberContentsByUser) + } else { + true + } + } + return timelineEventEntity +} + +internal fun ThreadSummaryEntity.Companion.createOrUpdate( + threadSummaryType: ThreadSummaryUpdateType, + realm: Realm, + roomId: String, + threadEventEntity: EventEntity? = null, + rootThreadEvent: Event? = null, + roomMemberContentsByUser: HashMap, + roomEntity: RoomEntity, + userId: String, + cryptoService: CryptoService? = null +) { + when (threadSummaryType) { + ThreadSummaryUpdateType.REPLACE -> { + rootThreadEvent?.eventId ?: return + rootThreadEvent.senderId ?: return + + val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return + + // Something is wrong with the server return + if (numberOfThreads <= 0) return + + val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also { + Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ") + } + + val rootThreadEventEntity = createEventEntity(roomId, rootThreadEvent, realm).also { + decryptIfNeeded(cryptoService, it, roomId) + } + val latestThreadEventEntity = createLatestEventEntity(roomId, rootThreadEvent, roomMemberContentsByUser, realm)?.also { + decryptIfNeeded(cryptoService, it, roomId) + } + val isUserParticipating = rootThreadEvent.unsignedData.relations.latestThread.isUserParticipating == true || rootThreadEvent.senderId == userId + roomMemberContentsByUser.addSenderState(realm, roomId, rootThreadEvent.senderId) + threadSummary.updateThreadSummary( + rootThreadEventEntity = rootThreadEventEntity, + numberOfThreads = numberOfThreads, + latestThreadEventEntity = latestThreadEventEntity, + isUserParticipating = isUserParticipating, + roomMemberContentsByUser = roomMemberContentsByUser + ) + + roomEntity.addIfNecessary(threadSummary) + } + ThreadSummaryUpdateType.ADD -> { + val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return + Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId") + + val threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId) + if (threadSummary != null) { + // ThreadSummary exists so lets add the latest event + Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.") + threadSummary.updateThreadSummaryLatestEvent(threadEventEntity, roomMemberContentsByUser) + threadSummary.numberOfThreads++ + if (threadEventEntity.sender == userId) { + threadSummary.isUserParticipating = true + } + } else { + // ThreadSummary do not exists lets try to create one + Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one") + threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity -> + // Root thread event entity exists so lets create a new record + ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).let { + it.updateThreadSummary( + rootThreadEventEntity = rootThreadEventEntity, + numberOfThreads = 1, + latestThreadEventEntity = threadEventEntity, + isUserParticipating = threadEventEntity.sender == userId, + roomMemberContentsByUser = roomMemberContentsByUser + ) + roomEntity.addIfNecessary(it) + } + } + } + } + } +} + +private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) { + cryptoService ?: return + val event = eventEntity.asDomain() + if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) { + try { + Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}") + // Event from sync does not have roomId, so add it to the event first + val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + // Save decryption result, to not decrypt every time we enter the thread list + eventEntity.setDecryptionResult(result) + } catch (e: MXCryptoError) { + if (e is MXCryptoError.Base) { + event.mCryptoError = e.errorType + event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription + } + } + } +} + +/** + * Request decryption + */ +private fun requestDecryption(eventDecryptor: TimelineEventDecryptor?, event: Event?) { + eventDecryptor ?: return + event ?: return + if (event.isEncrypted() && + event.mxDecryptionResult == null && event.eventId != null) { + Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}") + + eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(event, UUID.randomUUID().toString())) + } +} + +/** + * If we don't have any new state on this user, get it from db + */ +private fun HashMap.addSenderState(realm: Realm, roomId: String, senderId: String) { + getOrPut(senderId) { + CurrentStateEventEntity + .getOrNull(realm, roomId, senderId, EventType.STATE_ROOM_MEMBER) + ?.root?.asDomain() + ?.getFixedRoomMemberContent() + } +} + +/** + * Create an EventEntity for the root thread event or get an existing one + */ +private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { + val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } + return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) +} + +/** + * Create an EventEntity for the latest thread event or get an existing one. Also update the user room member + * state + */ +private fun createLatestEventEntity(roomId: String, rootThreadEvent: Event, roomMemberContentsByUser: HashMap, realm: Realm): EventEntity? { + return getLatestEvent(rootThreadEvent)?.let { + it.senderId?.let { senderId -> + roomMemberContentsByUser.addSenderState(realm, roomId, senderId) + } + createEventEntity(roomId, it, realm) + } +} + +/** + * Returned the latest event message, if any + */ +private fun getLatestEvent(rootThreadEvent: Event): Event? { + return rootThreadEvent.unsignedData?.relations?.latestThread?.event +} + +/** + * Find all ThreadSummaryEntity for the specified roomId, sorted by origin server + * note: Sorting cannot be provided by server, so we have to use that unstable property + * @param roomId The id of the room + */ +internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery = + ThreadSummaryEntity + .where(realm, roomId = roomId) + .sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING) + +/** + * Enhance each [ThreadSummary] root and latest event with the equivalent decrypted text edition/replacement + */ +internal fun List.enhanceWithEditions(realm: Realm, roomId: String): List = + this.map { + it.addEditionIfNeeded(realm, roomId, true) + it.addEditionIfNeeded(realm, roomId, false) + it + } + +private fun ThreadSummary.addEditionIfNeeded(realm: Realm, roomId: String, enhanceRoot: Boolean) { + val eventId = if (enhanceRoot) rootEventId else latestEvent?.eventId ?: return + EventAnnotationsSummaryEntity + .where(realm, roomId, eventId) + .findFirst() + ?.editSummary + ?.editions + ?.lastOrNull() + ?.eventId + ?.let { editedEventId -> + TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent -> + if (enhanceRoot) { + threadEditions.rootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() ?: "(edited)" + } else { + threadEditions.latestThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() ?: "(edited)" + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 7869506015..8be3455c07 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -41,7 +41,8 @@ internal object HomeServerCapabilitiesMapper { maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, defaultIdentityServerUrl = entity.defaultIdentityServerUrl, - roomVersions = mapRoomVersion(entity.roomVersionsJson) + roomVersions = mapRoomVersion(entity.roomVersionsJson), + canUseThreading = false // entity.canUseThreading ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt new file mode 100644 index 0000000000..54386c5ea8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.mapper + +import org.matrix.android.sdk.api.session.room.sender.SenderInfo +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity +import javax.inject.Inject + +internal class ThreadSummaryMapper @Inject constructor() { + + fun map(threadSummary: ThreadSummaryEntity): ThreadSummary { + return ThreadSummary( + roomId = threadSummary.room?.firstOrNull()?.roomId.orEmpty(), + rootEvent = threadSummary.rootThreadEventEntity?.asDomain(), + latestEvent = threadSummary.latestThreadEventEntity?.asDomain(), + rootEventId = threadSummary.rootThreadEventId, + rootThreadSenderInfo = SenderInfo( + userId = threadSummary.rootThreadEventEntity?.sender ?: "", + displayName = threadSummary.rootThreadSenderName, + isUniqueDisplayName = threadSummary.rootThreadIsUniqueDisplayName, + avatarUrl = threadSummary.rootThreadSenderAvatar + ), + latestThreadSenderInfo = SenderInfo( + userId = threadSummary.latestThreadEventEntity?.sender ?: "", + displayName = threadSummary.latestThreadSenderName, + isUniqueDisplayName = threadSummary.latestThreadIsUniqueDisplayName, + avatarUrl = threadSummary.latestThreadSenderAvatar + ), + isUserParticipating = threadSummary.isUserParticipating, + numberOfThreads = threadSummary.numberOfThreads + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt new file mode 100644 index 0000000000..b56b7d325b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import io.realm.FieldAttribute +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) { + + override fun doMigrate(realm: DynamicRealm) { + val eventEntity = realm.schema.get("EventEntity") ?: return + val threadSummaryEntity = realm.schema.create("ThreadSummaryEntity") + .addField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_AVATAR, String::class.java) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_AVATAR, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.NUMBER_OF_THREADS, Int::class.java) + .addField(ThreadSummaryEntityFields.IS_USER_PARTICIPATING, Boolean::class.java) + .addRealmObjectField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ENTITY.`$`, eventEntity) + .addRealmObjectField(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.`$`, eventEntity) + + realm.schema.get("RoomEntity") + ?.addRealmListField(RoomEntityFields.THREAD_SUMMARIES.`$`, threadSummaryEntity) + + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addRealmListField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index ca8049fd96..88eb821aa9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -50,13 +50,18 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, companion object } -internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) { +internal fun ChunkEntity.deleteOnCascade( + deleteStateEvents: Boolean, + canDeleteRoot: Boolean) { assertIsManaged() if (deleteStateEvents) { stateEvents.deleteAllFromRealm() } timelineEvents.clearWith { val deleteRoot = canDeleteRoot && (it.root?.stateKey == null || deleteStateEvents) + if (deleteRoot) { + room?.firstOrNull()?.removeThreadSummaryIfNeeded(it.eventId) + } it.deleteOnCascade(deleteRoot) } deleteFromRealm() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index 445181e576..b7158ba9cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -34,14 +34,14 @@ internal open class EventEntity(@Index var eventId: String = "", @Index var stateKey: String? = null, var originServerTs: Long? = null, @Index var sender: String? = null, - // Can contain a serialized MatrixError + // Can contain a serialized MatrixError var sendStateDetails: String? = null, var age: Long? = 0, var unsignedData: String? = null, var redacts: String? = null, var decryptionResultJson: String? = null, var ageLocalTs: Long? = null, - // Thread related, no need to create a new Entity for performance + // Thread related, no need to create a new Entity for performance @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, var numberOfThreads: Int = 0, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 08ecd5995e..47a83f0ed9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -28,7 +28,8 @@ internal open class HomeServerCapabilitiesEntity( var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, - var lastUpdatedTimestamp: Long = 0L + var lastUpdatedTimestamp: Long = 0L, + var canUseThreading: Boolean = false ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index 2997d5d7d8..4a6f6a7bf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -20,10 +20,14 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity +import org.matrix.android.sdk.internal.database.query.findRootOrLatest +import org.matrix.android.sdk.internal.extensions.assertIsManaged internal open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList(), var sendingTimelineEvents: RealmList = RealmList(), + var threadSummaries: RealmList = RealmList(), var accountData: RealmList = RealmList() ) : RealmObject() { @@ -46,3 +50,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "", } companion object } +internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) { + assertIsManaged() + threadSummaries.findRootOrLatest(eventId)?.let { + threadSummaries.remove(it) + it.deleteFromRealm() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index c090777972..d0d23dd491 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.annotations.RealmModule import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity /** * Realm module for Session @@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntit RoomAccountDataEntity::class, SpaceChildSummaryEntity::class, SpaceParentSummaryEntity::class, - UserPresenceEntity::class + UserPresenceEntity::class, + ThreadSummaryEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt new file mode 100644 index 0000000000..21a80502e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model.threads + +import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.Index +import io.realm.annotations.LinkingObjects +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.RoomEntity + +internal open class ThreadSummaryEntity(@Index var rootThreadEventId: String = "", + var rootThreadEventEntity: EventEntity? = null, + var latestThreadEventEntity: EventEntity? = null, + var rootThreadSenderName: String? = null, + var latestThreadSenderName: String? = null, + var rootThreadSenderAvatar: String? = null, + var latestThreadSenderAvatar: String? = null, + var rootThreadIsUniqueDisplayName: Boolean = false, + var isUserParticipating: Boolean = false, + var latestThreadIsUniqueDisplayName: Boolean = false, + var numberOfThreads: Int = 0 +) : RealmObject() { + + @LinkingObjects("threadSummaries") + val room: RealmResults? = null + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt new file mode 100644 index 0000000000..517d43d7cf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.RealmList +import io.realm.RealmQuery +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields + +internal fun ThreadSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery { + return realm.where() + .equalTo(ThreadSummaryEntityFields.ROOM.ROOM_ID, roomId) +} + +internal fun ThreadSummaryEntity.Companion.where(realm: Realm, roomId: String, rootThreadEventId: String): RealmQuery { + return where(realm, roomId) + .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) +} + +internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity { + return where(realm, roomId, rootThreadEventId).findFirst() ?: realm.createObject().apply { + this.rootThreadEventId = rootThreadEventId + } +} +internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? { + return where(realm, roomId, rootThreadEventId).findFirst() +} +internal fun RealmList.find(rootThreadEventId: String): ThreadSummaryEntity? { + return this.where() + .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .findFirst() +} + +internal fun RealmList.findRootOrLatest(eventId: String): ThreadSummaryEntity? { + return this.where() + .beginGroup() + .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, eventId) + .or() + .equalTo(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.EVENT_ID, eventId) + .endGroup() + .findFirst() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt index 7415b988a4..2e52354037 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt @@ -17,9 +17,21 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import timber.log.Timber internal object FilterFactory { + fun createThreadsFilter(numberOfEvents: Int, userId: String?): RoomEventFilter { + Timber.i("$userId") + return RoomEventFilter( + limit = numberOfEvents, +// senders = listOf(userId), +// relationSenders = userId?.let { listOf(it) }, + relationTypes = listOf(RelationType.IO_THREAD) + ) + } + fun createUploadsFilter(numberOfEvents: Int): RoomEventFilter { return RoomEventFilter( limit = numberOfEvents, @@ -58,8 +70,8 @@ internal object FilterFactory { private fun createElementTimelineFilter(): RoomEventFilter? { return null // RoomEventFilter().apply { - // TODO Enable this for optimization - // types = listOfSupportedEventTypes.toMutableList() + // TODO Enable this for optimization + // types = listOfSupportedEventTypes.toMutableList() // } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt index f498322967..c93f6a10db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt @@ -52,12 +52,15 @@ data class RoomEventFilter( * A list of relation types which must be exist pointing to the event being filtered. * If this list is absent then no filtering is done on relation types. */ - @Json(name = "relation_types") val relationTypes: List? = null, +// @Json(name = "relation_types") val relationTypes: List? = null, + @Json(name = "io.element.relation_types") val relationTypes: List? = null, // To be replaced with the above line after the release /** * A list of senders of relations which must exist pointing to the event being filtered. * If this list is absent then no filtering is done on relation types. */ - @Json(name = "relation_senders") val relationSenders: List? = null, +// @Json(name = "relation_senders") val relationSenders: List? = null, + @Json(name = "io.element.relation_senders") val relationSenders: List? = null, // To be replaced with the above line after the release + /** * A list of room IDs to include. If this list is absent then all rooms are included. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 830a58cd12..55526b41db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -65,7 +65,13 @@ internal data class Capabilities( * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. */ @Json(name = "m.room_versions") - val roomVersions: RoomVersions? = null + val roomVersions: RoomVersions? = null, + /** + * Capability to indicate if the server supports MSC3440 Threading + * True if the user can use m.thread relation, false otherwise + */ + @Json(name = "m.thread") + val threads: BooleanCapability? = null ) @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index e822cbdcdb..8c6bb626d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -121,6 +121,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) } + homeServerCapabilitiesEntity.canUseThreading = capabilities?.threads?.enabled.orTrue() } if (getMediaConfigResult != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 2d8c3e9c78..34e859e509 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.threads.ThreadsService +import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService @@ -56,6 +57,7 @@ internal class DefaultRoom(override val roomId: String, private val roomSummaryDataSource: RoomSummaryDataSource, private val timelineService: TimelineService, private val threadsService: ThreadsService, + private val threadsLocalService: ThreadsLocalService, private val sendService: SendService, private val draftService: DraftService, private val stateService: StateService, @@ -80,6 +82,7 @@ internal class DefaultRoom(override val roomId: String, Room, TimelineService by timelineService, ThreadsService by threadsService, + ThreadsLocalService by threadsLocalService, SendService by sendService, DraftService by draftService, StateService by stateService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 2eebb70bdc..d17d16a82d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -197,6 +197,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleReaction(realm, event, roomId, isLocalEcho) } } + // TODO is that ok?? +// else if (event.unsignedData?.relations?.annotations != null) { +// Timber.v("###REACTION e2e Aggregation in room $roomId for event ${event.eventId}") +// handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) +// // EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() +// // ?.let { +// // TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() +// // ?.forEach { tet -> tet.annotations = it } +// // } +// } } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } @@ -244,7 +254,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } // OPT OUT serer aggregation until API mature enough - private val SHOULD_HANDLE_SERVER_AGREGGATION = false + private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e private fun handleReplace(realm: Realm, event: Event, @@ -346,6 +356,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( /** * Check if the edition is on the latest thread event, and update it accordingly + * @param editedEvent The event that will be changed + * @param replaceEvent The new event */ private fun handleThreadSummaryEdition(editedEvent: EventEntity?, replaceEvent: TimelineEventEntity?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 86929e013f..71838ab5a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -86,7 +86,7 @@ internal interface RoomAPI { suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String, @Query("from") from: String, @Query("dir") dir: String, - @Query("limit") limit: Int, + @Query("limit") limit: Int?, @Query("filter") filter: String? ): PaginationResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 70c1ab4f42..72a3f9ab22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService +import org.matrix.android.sdk.internal.session.room.threads.local.DefaultThreadsLocalService import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService @@ -52,6 +53,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val roomSummaryDataSource: RoomSummaryDataSource, private val timelineServiceFactory: DefaultTimelineService.Factory, private val threadsServiceFactory: DefaultThreadsService.Factory, + private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory, private val sendServiceFactory: DefaultSendService.Factory, private val draftServiceFactory: DefaultDraftService.Factory, private val stateServiceFactory: DefaultStateService.Factory, @@ -79,6 +81,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomSummaryDataSource = roomSummaryDataSource, timelineService = timelineServiceFactory.create(roomId), threadsService = threadsServiceFactory.create(roomId), + threadsLocalService = threadsLocalServiceFactory.create(roomId), sendService = sendServiceFactory.create(roomId), draftService = draftServiceFactory.create(roomId), stateService = stateServiceFactory.create(roomId), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index f831a77a5d..5e90076b8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -77,7 +77,9 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultUpdateQuickR import org.matrix.android.sdk.internal.session.room.relation.FetchEditHistoryTask import org.matrix.android.sdk.internal.session.room.relation.FindReactionEventForUndoTask import org.matrix.android.sdk.internal.session.room.relation.UpdateQuickReactionTask +import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadSummariesTask import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportContentTask import org.matrix.android.sdk.internal.session.room.reporting.ReportContentTask @@ -294,4 +296,7 @@ internal abstract class RoomModule { @Binds abstract fun bindFetchThreadTimelineTask(task: DefaultFetchThreadTimelineTask): FetchThreadTimelineTask + + @Binds + abstract fun bindFetchThreadSummariesTask(task: DefaultFetchThreadSummariesTask): FetchThreadSummariesTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index d22583e8b7..f21ee4346c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.util.fetchCopyMap @@ -51,7 +50,6 @@ internal class DefaultRelationService @AssistedInject constructor( private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val fetchEditHistoryTask: FetchEditHistoryTask, - private val fetchThreadTimelineTask: FetchThreadTimelineTask, private val timelineEventMapper: TimelineEventMapper, @SessionDatabase private val monarchy: Monarchy ) : RelationService { @@ -205,16 +203,6 @@ internal class DefaultRelationService @AssistedInject constructor( return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } - override suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean { - fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( - roomId, - rootThreadEventId, - null, - 10 - )) - return true - } - /** * Saves the event in database as a local echo. * SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt new file mode 100644 index 0000000000..d316eed691 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.internal.session.room.relation.threads + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType +import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.database.helper.createOrUpdate +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.filter.FilterFactory +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import timber.log.Timber +import javax.inject.Inject + +/*** + * This class is responsible to Fetch all the thread in the current room, + * To fetch all threads in a room, the /messages API is used with newly added filtering options. + */ +internal interface FetchThreadSummariesTask : Task { + data class Params( + val roomId: String, + val from: String = "", + val limit: Int = 100, + val isUserParticipating: Boolean = true + ) +} + +internal class DefaultFetchThreadSummariesTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + @SessionDatabase private val monarchy: Monarchy, + private val cryptoService: DefaultCryptoService, + @UserId private val userId: String, +) : FetchThreadSummariesTask { + + override suspend fun execute(params: FetchThreadSummariesTask.Params): Result { + val filter = FilterFactory.createThreadsFilter( + numberOfEvents = params.limit, + userId = if (params.isUserParticipating) userId else null).toJSONString() + + val response = executeRequest( + globalErrorReceiver, + canRetry = true + ) { + roomAPI.getRoomMessagesFrom(params.roomId, params.from, PaginationDirection.BACKWARDS.value, params.limit, filter) + } + + Timber.i("###THREADS DefaultFetchThreadSummariesTask Fetched size:${response.events.size} nextBatch:${response.end} ") + + return handleResponse(response, params) + } + + private suspend fun handleResponse(response: PaginationResponse, + params: FetchThreadSummariesTask.Params): Result { + val rootThreadList = response.events + monarchy.awaitTransaction { realm -> + val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction + + val roomMemberContentsByUser = HashMap() + for (rootThreadEvent in rootThreadList) { + if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) { + continue + } + + ThreadSummaryEntity.createOrUpdate( + threadSummaryType = ThreadSummaryUpdateType.REPLACE, + realm = realm, + roomId = params.roomId, + rootThreadEvent = rootThreadEvent, + roomMemberContentsByUser = roomMemberContentsByUser, + roomEntity = roomEntity, + userId = userId, + cryptoService = cryptoService) + } + } + return Result.SUCCESS + } + + enum class Result { + SHOULD_FETCH_MORE, + REACHED_END, + SUCCESS + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index e7b91ebab7..54ebc620c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -58,7 +58,6 @@ import javax.inject.Inject /*** * This class is responsible to Fetch paginated chunks of the thread timeline using the /relations API * - * * How it works * * The problem? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt index 5967ae8d2e..033c1c0ff9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/DefaultThreadsService.kt @@ -23,25 +23,25 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.Realm import org.matrix.android.sdk.api.session.room.threads.ThreadsService -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.threads.ThreadNotificationState -import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary +import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId -import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread -import org.matrix.android.sdk.internal.database.helper.mapEventsWithEdition +import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask +import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask internal class DefaultThreadsService @AssistedInject constructor( @Assisted private val roomId: String, @UserId private val userId: String, + private val fetchThreadTimelineTask: FetchThreadTimelineTask, + private val fetchThreadSummariesTask: FetchThreadSummariesTask, @SessionDatabase private val monarchy: Monarchy, private val timelineEventMapper: TimelineEventMapper, + private val threadSummaryMapper: ThreadSummaryMapper ) : ThreadsService { @AssistedFactory @@ -49,55 +49,40 @@ internal class DefaultThreadsService @AssistedInject constructor( fun create(roomId: String): DefaultThreadsService } - override fun getMarkedThreadNotificationsLive(): LiveData> { + override fun getAllThreadSummariesLive(): LiveData> { return monarchy.findAllMappedWithChanges( - { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, - { timelineEventMapper.map(it) } + { ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { + threadSummaryMapper.map(it) + } ) } - override fun getMarkedThreadNotifications(): List { + override fun getAllThreadSummaries(): List { return monarchy.fetchAllMappedSync( - { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, - { timelineEventMapper.map(it) } + { ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { threadSummaryMapper.map(it) } ) } - override fun getAllThreadsLive(): LiveData> { - return monarchy.findAllMappedWithChanges( - { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, - { timelineEventMapper.map(it) } - ) - } - - override fun getAllThreads(): List { - return monarchy.fetchAllMappedSync( - { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, - { timelineEventMapper.map(it) } - ) - } - - override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean { + override fun enhanceWithEditions(threads: List): List { return Realm.getInstance(monarchy.realmConfiguration).use { - TimelineEventEntity.isUserParticipatingInThread( - realm = it, - roomId = roomId, - rootThreadEventId = rootThreadEventId, - senderId = userId) + threads.enhanceWithEditions(it, roomId) } } - override fun mapEventsWithEdition(threads: List): List { - return Realm.getInstance(monarchy.realmConfiguration).use { - threads.mapEventsWithEdition(it, roomId) - } + override suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int) { + fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( + roomId = roomId, + rootThreadEventId = rootThreadEventId, + from = from, + limit = limit + )) } - override suspend fun markThreadAsRead(rootThreadEventId: String) { - monarchy.awaitTransaction { - EventEntity.where( - realm = it, - eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE - } + override suspend fun fetchThreadSummaries() { + fetchThreadSummariesTask.execute(FetchThreadSummariesTask.Params( + roomId = roomId + )) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt new file mode 100644 index 0000000000..3bc36fb2a8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/threads/local/DefaultThreadsLocalService.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.threads.local + +import androidx.lifecycle.LiveData +import com.zhuinden.monarchy.Monarchy +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.realm.Realm +import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.threads.ThreadNotificationState +import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId +import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId +import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread +import org.matrix.android.sdk.internal.database.helper.mapEventsWithEdition +import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.util.awaitTransaction + +internal class DefaultThreadsLocalService @AssistedInject constructor( + @Assisted private val roomId: String, + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val timelineEventMapper: TimelineEventMapper, +) : ThreadsLocalService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultThreadsLocalService + } + + override fun getMarkedThreadNotificationsLive(): LiveData> { + return monarchy.findAllMappedWithChanges( + { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + + override fun getMarkedThreadNotifications(): List { + return monarchy.fetchAllMappedSync( + { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + + override fun getAllThreadsLive(): LiveData> { + return monarchy.findAllMappedWithChanges( + { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + + override fun getAllThreads(): List { + return monarchy.fetchAllMappedSync( + { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + + override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean { + return Realm.getInstance(monarchy.realmConfiguration).use { + TimelineEventEntity.isUserParticipatingInThread( + realm = it, + roomId = roomId, + rootThreadEventId = rootThreadEventId, + senderId = userId) + } + } + + override fun mapEventsWithEdition(threads: List): List { + return Realm.getInstance(monarchy.realmConfiguration).use { + threads.mapEventsWithEdition(it, roomId) + } + } + + override suspend fun markThreadAsRead(rootThreadEventId: String) { + monarchy.awaitTransaction { + EventEntity.where( + realm = it, + eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 573af7c696..63857d611b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -23,10 +23,12 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.api.session.sync.model.RoomSync @@ -36,6 +38,7 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addTimelineEvent +import org.matrix.android.sdk.internal.database.helper.createOrUpdate import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -48,6 +51,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom @@ -60,6 +64,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask @@ -86,6 +91,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val threadsAwarenessHandler: ThreadsAwarenessHandler, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @UserId private val userId: String, + private val homeServerCapabilitiesService: HomeServerCapabilitiesService, private val lightweightSettingsStorage: LightweightSettingsStorage, private val timelineInput: TimelineInput, private val liveEventService: Lazy) { @@ -345,7 +351,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return roomEntity } - val customList = arrayListOf() private fun handleTimelineEvents(realm: Realm, roomId: String, roomEntity: RoomEntity, @@ -420,6 +425,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle optimizedThreadSummaryMap[it] = eventEntity // Add the same thread timeline event to Thread Chunk addToThreadChunkIfNeeded(realm, roomId, it, timelineEventAdded, roomEntity) + // Add thread list if needed + if(homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreading) { + ThreadSummaryEntity.createOrUpdate( + threadSummaryType = ThreadSummaryUpdateType.ADD, + realm = realm, + roomId = roomId, + threadEventEntity = eventEntity, + roomMemberContentsByUser = roomMemberContentsByUser, + userId = userId, + roomEntity = roomEntity) + } } ?: run { // This is a normal event or a root thread one optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index ca18060c51..fc76535c4c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -32,7 +32,6 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.views.ThreadListFragment -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @AndroidEntryPoint @@ -92,14 +91,7 @@ class ThreadsActivity : VectorBaseActivity() { * This function is used to navigate to the selected thread timeline. * One usage of that is from the Threads Activity */ - fun navigateToThreadTimeline( - timelineEvent: TimelineEvent) { - val roomThreadDetailArgs = ThreadTimelineArgs( - roomId = timelineEvent.roomId, - displayName = timelineEvent.senderInfo.displayName, - avatarUrl = timelineEvent.senderInfo.avatarUrl, - roomEncryptionTrustLevel = null, - rootThreadEventId = timelineEvent.eventId) + fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) { val commonOption: (FragmentTransaction) -> Unit = { it.setCustomAnimations( R.anim.animation_slide_in_right, @@ -111,8 +103,8 @@ class ThreadsActivity : VectorBaseActivity() { container = views.threadsActivityFragmentContainer, fragmentClass = TimelineFragment::class.java, params = TimelineArgs( - roomId = timelineEvent.roomId, - threadTimelineArgs = roomThreadDetailArgs + roomId = threadTimelineArgs.roomId, + threadTimelineArgs = threadTimelineArgs ), option = commonOption ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index 32cb006810..d3a5497d63 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -23,15 +23,19 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.threads.list.model.threadListItem +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.api.util.toMatrixItemOrNull import javax.inject.Inject class ThreadListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, - private val dateFormatter: VectorDateFormatter + private val dateFormatter: VectorDateFormatter, + private val session: Session ) : EpoxyController() { var listener: Listener? = null @@ -43,10 +47,59 @@ class ThreadListController @Inject constructor( requestModelBuild() } - override fun buildModels() { + override fun buildModels() = + when (session.getHomeServerCapabilities().canUseThreading) { + true -> buildThreadSummaries() + false -> buildThreadList() + } + + /** + * Building thread summaries when homeserver + * supports threading + */ + private fun buildThreadSummaries() { val safeViewState = viewState ?: return val host = this + safeViewState.threadSummaryList.invoke() + ?.filter { + if (safeViewState.shouldFilterThreads) { + it.isUserParticipating + } else { + true + } + } + ?.forEach { threadSummary -> + val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST) + val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message) + val rootThreadEdition = threadSummary.threadEditions.rootThreadEdition + val latestThreadEdition = threadSummary.threadEditions.latestThreadEdition + threadListItem { + id(threadSummary.rootEvent?.eventId) + avatarRenderer(host.avatarRenderer) + matrixItem(threadSummary.rootThreadSenderInfo.toMatrixItem()) + title(threadSummary.rootThreadSenderInfo.displayName.orEmpty()) + date(date) + rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false) + // TODO refactor notifications that with the new thread summary + threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE) + rootMessage(rootThreadEdition ?: threadSummary.rootEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage) + lastMessage(latestThreadEdition ?: threadSummary.latestEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage) + lastMessageCounter(threadSummary.numberOfThreads.toString()) + lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull()) + itemClickListener { + host.listener?.onThreadSummaryClicked(threadSummary) + } + } + } + } + /** + * Building local thread list when homeserver do not + * support threading + */ + private fun buildThreadList() { + val safeViewState = viewState ?: return + val host = this safeViewState.rootThreadEventList.invoke() ?.filter { if (safeViewState.shouldFilterThreads) { @@ -74,13 +127,14 @@ class ThreadListController @Inject constructor( lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) itemClickListener { - host.listener?.onThreadClicked(timelineEvent) + host.listener?.onThreadListClicked(timelineEvent) } } } } interface Listener { - fun onThreadClicked(timelineEvent: TimelineEvent) + fun onThreadSummaryClicked(threadSummary: ThreadSummary) + fun onThreadListClicked(timelineEvent: TimelineEvent) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index d82b5d6ccf..290b71a504 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent import org.matrix.android.sdk.flow.flow @@ -53,11 +54,41 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState } init { - observeThreadsList() + observeThreads() + fetchThreadList() } override fun handle(action: EmptyAction) {} + /** + * Observing thread list with respect to homeserver + * capabilities + */ + private fun observeThreads() { + when (session.getHomeServerCapabilities().canUseThreading) { + true -> observeThreadSummaries() + false -> observeThreadsList() + } + } + + /** + * Observing thread summaries when homeserver support + * threading + */ + private fun observeThreadSummaries() { + room?.flow() + ?.liveThreadSummaries() + ?.map { room.enhanceWithEditions(it) } + ?.flowOn(room.coroutineDispatchers.io) + ?.execute { asyncThreads -> + copy(threadSummaryList = asyncThreads) + } + } + + /** + * Observing thread list when homeserver do not support + * threading + */ private fun observeThreadsList() { room?.flow() ?.liveThreadList() @@ -74,6 +105,14 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState } } + private fun fetchThreadList() { + viewModelScope.launch { + room?.fetchThreadSummaries() + } + } + + fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading + fun applyFiltering(shouldFilterThreads: Boolean) { setState { copy(shouldFilterThreads = shouldFilterThreads) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt index 2a70a5be1e..e08f70030b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt @@ -20,13 +20,14 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent data class ThreadListViewState( + val threadSummaryList: Async> = Uninitialized, val rootThreadEventList: Async> = Uninitialized, val shouldFilterThreads: Boolean = false, val roomId: String ) : MavericksState { - constructor(args: ThreadListArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index 180e6226d0..949778629b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -34,9 +34,11 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator import im.vector.app.features.home.room.threads.ThreadsActivity import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState +import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -111,12 +113,30 @@ class ThreadListFragment @Inject constructor( views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName } - override fun onThreadClicked(timelineEvent: TimelineEvent) { - (activity as? ThreadsActivity)?.navigateToThreadTimeline(timelineEvent) + override fun onThreadSummaryClicked(threadSummary: ThreadSummary) { + val roomThreadDetailArgs = ThreadTimelineArgs( + roomId = threadSummary.roomId, + displayName = threadSummary.rootThreadSenderInfo.displayName, + avatarUrl = threadSummary.rootThreadSenderInfo.avatarUrl, + roomEncryptionTrustLevel = null, + rootThreadEventId = threadSummary.rootEventId) + (activity as? ThreadsActivity)?.navigateToThreadTimeline(roomThreadDetailArgs) + } + + override fun onThreadListClicked(timelineEvent: TimelineEvent) { + val threadTimelineArgs = ThreadTimelineArgs( + roomId = timelineEvent.roomId, + displayName = timelineEvent.senderInfo.displayName, + avatarUrl = timelineEvent.senderInfo.avatarUrl, + roomEncryptionTrustLevel = null, + rootThreadEventId = timelineEvent.eventId) + (activity as? ThreadsActivity)?.navigateToThreadTimeline(threadTimelineArgs) } private fun renderEmptyStateIfNeeded(state: ThreadListViewState) { - val show = state.rootThreadEventList.invoke().isNullOrEmpty() - views.threadListEmptyConstraintLayout.isVisible = show + when (threadListViewModel.canHomeserverUseThreading()) { + true -> views.threadListEmptyConstraintLayout.isVisible = state.threadSummaryList.invoke().isNullOrEmpty() + false -> views.threadListEmptyConstraintLayout.isVisible = state.rootThreadEventList.invoke().isNullOrEmpty() + } } } From f4f48b919e13367413f5211b77bd5e5652d85db2 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 21 Feb 2022 12:14:51 +0200 Subject: [PATCH 0007/1430] Improve home server capabilities for threads --- .../internal/database/mapper/HomeServerCapabilitiesMapper.kt | 2 +- .../session/homeserver/GetHomeServerCapabilitiesTask.kt | 3 ++- .../internal/session/sync/handler/room/RoomSyncHandler.kt | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 8be3455c07..2e33988a22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -42,7 +42,7 @@ internal object HomeServerCapabilitiesMapper { lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, defaultIdentityServerUrl = entity.defaultIdentityServerUrl, roomVersions = mapRoomVersion(entity.roomVersionsJson), - canUseThreading = false // entity.canUseThreading + canUseThreading = entity.canUseThreading ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 8c6bb626d1..ae8e31c8a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions @@ -121,7 +122,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) } - homeServerCapabilitiesEntity.canUseThreading = capabilities?.threads?.enabled.orTrue() + homeServerCapabilitiesEntity.canUseThreading = capabilities?.threads?.enabled.orFalse() } if (getMediaConfigResult != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 63857d611b..591b3c2ed5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -64,7 +64,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent -import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask @@ -425,8 +424,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle optimizedThreadSummaryMap[it] = eventEntity // Add the same thread timeline event to Thread Chunk addToThreadChunkIfNeeded(realm, roomId, it, timelineEventAdded, roomEntity) - // Add thread list if needed - if(homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreading) { + if (homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreading) { + // Update thread summaries only if homeserver supports threading ThreadSummaryEntity.createOrUpdate( threadSummaryType = ThreadSummaryUpdateType.ADD, realm = realm, From 2b740a1ab662965c24909f53d6c52c0f80284836 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 21 Feb 2022 17:23:17 +0200 Subject: [PATCH 0008/1430] Implement permalink support for /relations live thread timeline --- .../session/room/timeline/DefaultTimeline.kt | 8 ++++++- .../session/room/timeline/TimelineChunk.kt | 3 ++- .../home/room/detail/TimelineViewModel.kt | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 5662986663..8c2b4d2bbe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -301,7 +301,13 @@ internal class DefaultTimeline(private val roomId: String, Timber.v("Post snapshot of ${snapshot.size} events") withContext(coroutineDispatchers.main) { listeners.forEach { - tryOrNull { it.onTimelineUpdated(snapshot) } + if (initialEventId != null && isFromThreadTimeline && snapshot.firstOrNull { it.eventId == initialEventId } == null) { + // We are in a thread timeline with a permalink, post update timeline only when the appropriate message have been found + tryOrNull { it.onTimelineUpdated(arrayListOf()) } + } else { + // In all the other cases update timeline as expected + tryOrNull { it.onTimelineUpdated(snapshot) } + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 864b3f0dd9..34aa83f9c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -171,11 +171,12 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, * always fetch results, while we want our data to be up to dated. */ suspend fun loadMoreThread(count: Int, direction: Timeline.Direction): LoadMoreResult { + val rootThreadEventId = timelineSettings.rootThreadEventId ?: return LoadMoreResult.FAILURE return if (direction == Timeline.Direction.BACKWARDS) { try { fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params( roomId, - timelineSettings.rootThreadEventId!!, + rootThreadEventId, chunkEntity.prevToken, count )).toLoadMoreResult() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index a404f9136b..b882990e30 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -156,6 +156,9 @@ class TimelineViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { const val PAGINATION_COUNT = 50 + // The larger the number the faster the results, COUNT=200 for 500 thread messages its x4 faster than COUNT=50 + const val PAGINATION_COUNT_THREADS_PERMALINK = 200 + } init { @@ -1174,10 +1177,30 @@ class TimelineViewModel @AssistedInject constructor( } } + /** + * Navigates to the appropriate event (by paginating the thread timeline until the event is found + * in the snapshot. The main reason for this function is to support the /relations api + */ + private var threadPermalinkHandled = false + private fun navigateToThreadEventIfNeeded(snapshot: List) { + if (eventId != null && initialState.rootThreadEventId != null) { + // When we have a permalink and we are in a thread timeline + if (snapshot.firstOrNull { it.eventId == eventId } != null && !threadPermalinkHandled) { + // Permalink event found lets navigate there + handleNavigateToEvent(RoomDetailAction.NavigateToEvent(eventId, true)) + threadPermalinkHandled = true + } else { + // Permalink event not found yet continue paginating + timeline.paginate(Timeline.Direction.BACKWARDS, PAGINATION_COUNT_THREADS_PERMALINK) + } + } + } + override fun onTimelineUpdated(snapshot: List) { viewModelScope.launch { // tryEmit doesn't work with SharedFlow without cache timelineEvents.emit(snapshot) + navigateToThreadEventIfNeeded(snapshot) } } From 0f721d971c1fe2e58406598c8d91cf3decb7e814 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 21 Feb 2022 17:24:34 +0200 Subject: [PATCH 0009/1430] Ktlint format --- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index b882990e30..a831332407 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -156,9 +156,9 @@ class TimelineViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { const val PAGINATION_COUNT = 50 + // The larger the number the faster the results, COUNT=200 for 500 thread messages its x4 faster than COUNT=50 const val PAGINATION_COUNT_THREADS_PERMALINK = 200 - } init { From deb86d2e874d00c8e895b4aa7de34266cae8dd69 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Feb 2022 13:18:09 +0200 Subject: [PATCH 0010/1430] Resolve real migration conflicts --- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/migration/MigrateSessionTo026.kt | 28 +++++++++++ .../database/migration/MigrateSessionTo027.kt | 49 ------------------- 3 files changed, 29 insertions(+), 52 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 24ac310653..a57397dad5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -44,7 +44,6 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 -import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -59,7 +58,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 27L + val schemaVersion = 26L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -90,6 +89,5 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 24) MigrateSessionTo024(realm).perform() if (oldVersion < 25) MigrateSessionTo025(realm).perform() if (oldVersion < 26) MigrateSessionTo026(realm).perform() - if (oldVersion < 27) MigrateSessionTo027(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt index 597d6d1cbe..04b64c2893 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt @@ -19,9 +19,17 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import io.realm.FieldAttribute import org.matrix.android.sdk.internal.database.model.ChunkEntityFields +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator +/** + * Migrating to: + * Live thread list: using enhanced /messages api MSC3440 + * Live thread timeline: using /relations api + */ class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { override fun doMigrate(realm: DynamicRealm) { @@ -31,5 +39,25 @@ class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { realm.schema.get("TimelineEventEntity") ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java) + + val eventEntity = realm.schema.get("EventEntity") ?: return + val threadSummaryEntity = realm.schema.create("ThreadSummaryEntity") + .addField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_AVATAR, String::class.java) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_AVATAR, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.NUMBER_OF_THREADS, Int::class.java) + .addField(ThreadSummaryEntityFields.IS_USER_PARTICIPATING, Boolean::class.java) + .addRealmObjectField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ENTITY.`$`, eventEntity) + .addRealmObjectField(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.`$`, eventEntity) + + realm.schema.get("RoomEntity") + ?.addRealmListField(RoomEntityFields.THREAD_SUMMARIES.`$`, threadSummaryEntity) + + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addRealmListField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt deleted file mode 100644 index b56b7d325b..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.database.migration - -import io.realm.DynamicRealm -import io.realm.FieldAttribute -import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields -import org.matrix.android.sdk.internal.database.model.RoomEntityFields -import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields -import org.matrix.android.sdk.internal.util.database.RealmMigrator - -class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) { - - override fun doMigrate(realm: DynamicRealm) { - val eventEntity = realm.schema.get("EventEntity") ?: return - val threadSummaryEntity = realm.schema.create("ThreadSummaryEntity") - .addField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) - .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_NAME, String::class.java) - .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_AVATAR, String::class.java) - .addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) - .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_NAME, String::class.java) - .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_AVATAR, String::class.java) - .addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) - .addField(ThreadSummaryEntityFields.NUMBER_OF_THREADS, Int::class.java) - .addField(ThreadSummaryEntityFields.IS_USER_PARTICIPATING, Boolean::class.java) - .addRealmObjectField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ENTITY.`$`, eventEntity) - .addRealmObjectField(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.`$`, eventEntity) - - realm.schema.get("RoomEntity") - ?.addRealmListField(RoomEntityFields.THREAD_SUMMARIES.`$`, threadSummaryEntity) - - realm.schema.get("HomeServerCapabilitiesEntity") - ?.addRealmListField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java) - } -} From 9953d0d0ed095f6c144e80e9ac0fbd724462c2a6 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Feb 2022 13:57:43 +0200 Subject: [PATCH 0011/1430] Resolve realm migration conflicts --- .../sdk/internal/database/mapper/ThreadSummaryMapper.kt | 2 +- .../sdk/internal/database/migration/MigrateSessionTo026.kt | 6 +++--- .../internal/database/model/threads/ThreadSummaryEntity.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt index 54386c5ea8..cedb9e3d45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ThreadSummaryMapper.kt @@ -28,7 +28,7 @@ internal class ThreadSummaryMapper @Inject constructor() { roomId = threadSummary.room?.firstOrNull()?.roomId.orEmpty(), rootEvent = threadSummary.rootThreadEventEntity?.asDomain(), latestEvent = threadSummary.latestThreadEventEntity?.asDomain(), - rootEventId = threadSummary.rootThreadEventId, + rootEventId = threadSummary.rootThreadEventId.orEmpty(), rootThreadSenderInfo = SenderInfo( userId = threadSummary.rootThreadEventEntity?.sender ?: "", displayName = threadSummary.rootThreadSenderName, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt index 04b64c2893..ac097c916b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt @@ -45,10 +45,10 @@ class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { .addField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_NAME, String::class.java) .addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_AVATAR, String::class.java) - .addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, Boolean::class.java) .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_NAME, String::class.java) .addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_AVATAR, String::class.java) - .addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java) + .addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, Boolean::class.java) .addField(ThreadSummaryEntityFields.NUMBER_OF_THREADS, Int::class.java) .addField(ThreadSummaryEntityFields.IS_USER_PARTICIPATING, Boolean::class.java) .addRealmObjectField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ENTITY.`$`, eventEntity) @@ -58,6 +58,6 @@ class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { ?.addRealmListField(RoomEntityFields.THREAD_SUMMARIES.`$`, threadSummaryEntity) realm.schema.get("HomeServerCapabilitiesEntity") - ?.addRealmListField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java) + ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt index 21a80502e7..ab9d66548e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/threads/ThreadSummaryEntity.kt @@ -23,7 +23,7 @@ import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.RoomEntity -internal open class ThreadSummaryEntity(@Index var rootThreadEventId: String = "", +internal open class ThreadSummaryEntity(@Index var rootThreadEventId: String? = "", var rootThreadEventEntity: EventEntity? = null, var latestThreadEventEntity: EventEntity? = null, var rootThreadSenderName: String? = null, From 2054c577f3ad4e74362ad4ceb8bfe50f1ac04c57 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Feb 2022 17:41:54 +0200 Subject: [PATCH 0012/1430] Fix quality check errors --- .../sdk/api/session/room/threads/model/ThreadEditions.kt | 4 ++-- .../sdk/internal/database/helper/ThreadSummaryHelper.kt | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt index db92e800e4..05d6fd52cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index d19056adfa..2d1b1cccf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -275,7 +275,11 @@ private fun createEventEntity(roomId: String, event: Event, realm: Realm): Event * Create an EventEntity for the latest thread event or get an existing one. Also update the user room member * state */ -private fun createLatestEventEntity(roomId: String, rootThreadEvent: Event, roomMemberContentsByUser: HashMap, realm: Realm): EventEntity? { +private fun createLatestEventEntity( + roomId: String, + rootThreadEvent: Event, + roomMemberContentsByUser: HashMap, + realm: Realm): EventEntity? { return getLatestEvent(rootThreadEvent)?.let { it.senderId?.let { senderId -> roomMemberContentsByUser.addSenderState(realm, roomId, senderId) From f7f363ce257c9cbc66f9cca81b89f974ba860741 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Feb 2022 20:52:01 +0200 Subject: [PATCH 0013/1430] Fix wrong copyrights --- .../sdk/api/session/room/threads/model/ThreadSummary.kt | 4 ++-- .../api/session/room/threads/model/ThreadSummaryUpdateType.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt index f26be85e85..017afba1ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummary.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt index 744265cb94..95697f987f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadSummaryUpdateType.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, From 79c97ac5129ea530f56bf8c870bfe10555fa6833 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 22 Feb 2022 20:59:22 +0200 Subject: [PATCH 0014/1430] Formating code --- .../api/session/room/threads/model/ThreadEditions.kt | 2 +- .../internal/database/helper/ThreadSummaryHelper.kt | 10 +--------- .../room/EventRelationsAggregationProcessor.kt | 12 ++++++------ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt index 05d6fd52cd..c8353cf0de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/model/ThreadEditions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index 2d1b1cccf4..229e433a89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -56,17 +56,9 @@ internal fun ThreadSummaryEntity.updateThreadSummary( roomMemberContentsByUser: HashMap) { updateThreadSummaryRootEvent(rootThreadEventEntity, roomMemberContentsByUser) updateThreadSummaryLatestEvent(latestThreadEventEntity, roomMemberContentsByUser) - - // Update latest event -// latestThreadEventEntity?.toTimelineEventEntity(roomMemberContentsByUser)?.let { -// Timber.i("###THREADS FetchThreadSummariesTask ThreadSummaryEntity updated latest event:${it.eventId} !") -// this.eventEntity?.threadSummaryLatestMessage = it -// } - - // Update number of threads this.isUserParticipating = isUserParticipating numberOfThreads?.let { - // Update only when there is an actual value + // Update number of threads only when there is an actual value this.numberOfThreads = it } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index d17d16a82d..573aba1aca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -197,15 +197,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleReaction(realm, event, roomId, isLocalEcho) } } - // TODO is that ok?? + // HandleInitialAggregatedRelations should also be applied in encrypted messages with annotations // else if (event.unsignedData?.relations?.annotations != null) { // Timber.v("###REACTION e2e Aggregation in room $roomId for event ${event.eventId}") // handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) -// // EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() -// // ?.let { -// // TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() -// // ?.forEach { tet -> tet.annotations = it } -// // } +// EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() +// ?.let { +// TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() +// ?.forEach { tet -> tet.annotations = it } +// } // } } EventType.REDACTION -> { From a9b3882bf6b93cd5a30521eaa01217350b8d62b0 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Feb 2022 12:06:35 +0200 Subject: [PATCH 0015/1430] Add changelogs --- changelog.d/5230.feature | 1 + changelog.d/5232.feature | 1 + changelog.d/5271.sdk | 1 + 3 files changed, 3 insertions(+) create mode 100644 changelog.d/5230.feature create mode 100644 changelog.d/5232.feature create mode 100644 changelog.d/5271.sdk diff --git a/changelog.d/5230.feature b/changelog.d/5230.feature new file mode 100644 index 0000000000..b333a3f2c7 --- /dev/null +++ b/changelog.d/5230.feature @@ -0,0 +1 @@ +Thread timeline is now live and much faster especially for large or old threads \ No newline at end of file diff --git a/changelog.d/5232.feature b/changelog.d/5232.feature new file mode 100644 index 0000000000..8f3bec97bd --- /dev/null +++ b/changelog.d/5232.feature @@ -0,0 +1 @@ +View all threads per room screen is now live when the home server supports threads \ No newline at end of file diff --git a/changelog.d/5271.sdk b/changelog.d/5271.sdk new file mode 100644 index 0000000000..b73d97ee4f --- /dev/null +++ b/changelog.d/5271.sdk @@ -0,0 +1 @@ +Adds support for MSC3440, additional threads homeserver capabilities \ No newline at end of file From 8788fb974d081f3e67fac56c26eb2427c7562c7f Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 23 Feb 2022 18:39:02 +0200 Subject: [PATCH 0016/1430] Add new use case about threads in the allScreensTest --- .../vector/app/ui/UiAllScreensSanityTest.kt | 24 ++++++++++++++ .../im/vector/app/ui/robot/ElementRobot.kt | 27 ++++++++++++++-- .../vector/app/ui/robot/MessageMenuRobot.kt | 9 ++++++ .../im/vector/app/ui/robot/RoomDetailRobot.kt | 31 ++++++++++++++++++- .../app/ui/robot/settings/SettingsRobot.kt | 10 ++++-- .../app/ui/robot/settings/labs/LabFeature.kt | 26 ++++++++++++++++ 6 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 417d28d625..5a03d5890a 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -27,6 +27,7 @@ import im.vector.app.espresso.tools.ScreenshotFailureRule import im.vector.app.features.MainActivity import im.vector.app.getString import im.vector.app.ui.robot.ElementRobot +import im.vector.app.ui.robot.settings.labs.LabFeature import im.vector.app.ui.robot.withDeveloperMode import org.junit.Rule import org.junit.Test @@ -97,6 +98,8 @@ class UiAllScreensSanityTest { } } + testThreadScreens() + elementRobot.space { createSpace { crawl() @@ -148,4 +151,25 @@ class UiAllScreensSanityTest { // TODO Deactivate account instead of logout? elementRobot.signout(expectSignOutWarning = false) } + + /** + * Testing multiple threads screens + */ + private fun testThreadScreens() { + elementRobot.toggleLabFeature(LabFeature.THREAD_MESSAGES) + elementRobot.newRoom { + createNewRoom { + crawl() + createRoom { + val message = "Hello This message will be a thread!" + postMessage(message) + replyToThread(message) + viewInRoom(message) + openThreadSummaries() + selectThreadSummariesFilter() + } + } + } + elementRobot.toggleLabFeature(LabFeature.THREAD_MESSAGES) + } } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index f0ce23b7db..3c5de8b221 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -17,9 +17,15 @@ package im.vector.app.ui.robot import android.view.View +import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickDialogNegativeButton @@ -35,6 +41,7 @@ import im.vector.app.features.home.HomeActivity import im.vector.app.features.onboarding.OnboardingActivity import im.vector.app.initialSyncIdlingResource import im.vector.app.ui.robot.settings.SettingsRobot +import im.vector.app.ui.robot.settings.labs.LabFeature import im.vector.app.ui.robot.space.SpaceRobot import im.vector.app.withIdlingResource import timber.log.Timber @@ -70,11 +77,11 @@ class ElementRobot { } } - fun settings(block: SettingsRobot.() -> Unit) { + fun settings(shouldGoBack: Boolean = true, block: SettingsRobot.() -> Unit) { openDrawer() clickOn(R.id.homeDrawerHeaderSettingsView) block(SettingsRobot()) - pressBack() + if (shouldGoBack) pressBack() waitUntilViewVisible(withId(R.id.bottomNavigationView)) } @@ -103,6 +110,22 @@ class ElementRobot { waitUntilViewVisible(withId(R.id.bottomNavigationView)) } + fun toggleLabFeature(labFeature: LabFeature) { + when (labFeature) { + LabFeature.THREAD_MESSAGES -> { + settings(shouldGoBack = false) { + labs(shouldGoBack = false) { + onView(withText(R.string.labs_enable_thread_messages)) + .check(ViewAssertions.matches(isDisplayed())) + .perform(ViewActions.closeSoftKeyboard(), click()) + } + } + } + else -> { + } + } + } + fun signout(expectSignOutWarning: Boolean) { clickOn(R.id.groupToolbarAvatarImageView) clickOn(R.id.homeDrawerHeaderSignoutView) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt index 5973dc3473..5c9ecfdef5 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt @@ -70,4 +70,13 @@ class MessageMenuRobot( clickOn(R.string.edit) autoClosed = true } + + fun replyInThread() { + clickOn(R.string.reply_in_thread) + autoClosed = true + } + fun viewInRoom() { + clickOn(R.string.view_in_room) + autoClosed = true + } } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt index 6cf6ad3551..91409582d9 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt @@ -62,6 +62,23 @@ class RoomDetailRobot { pressBack() } + fun replyToThread(message: String) { + openMessageMenu(message) { + replyInThread() + } + val threadMessage = "Hello universe - long message to avoid espresso tapping edited!" + writeTo(R.id.composerEditText, threadMessage) + waitUntilViewVisible(withId(R.id.sendButton)) + clickOn(R.id.sendButton) + } + + fun viewInRoom(message: String) { + openMessageMenu(message) { + viewInRoom() + } + waitUntilViewVisible(withId(R.id.composerEditText)) + } + fun crawlMessage(message: String) { // Test quick reaction val quickReaction = EmojiDataSource.quickEmojis[0] // 👍 @@ -110,7 +127,7 @@ class RoomDetailRobot { onView(withId(R.id.timelineRecyclerView)) .perform( RecyclerViewActions.actionOnItem( - ViewMatchers.hasDescendant(ViewMatchers.withText(message)), + ViewMatchers.hasDescendant(withText(message)), ViewActions.longClick() ) ) @@ -130,4 +147,16 @@ class RoomDetailRobot { block(RoomSettingsRobot()) pressBack() } + + fun openThreadSummaries() { + clickMenu(R.id.menu_timeline_thread_list) + waitUntilViewVisible(withId(R.id.threadListRecyclerView)) + } + + fun selectThreadSummariesFilter() { + clickMenu(R.id.menu_thread_list_filter) + sleep(1000) + clickOn(R.id.threadListModalMyThreads) + pressBack() + } } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt index 561f14c6f2..97aee7ac4a 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt @@ -16,6 +16,7 @@ package im.vector.app.ui.robot.settings +import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import im.vector.app.R import im.vector.app.clickOnAndGoBack @@ -51,8 +52,13 @@ class SettingsRobot { clickOnAndGoBack(R.string.settings_security_and_privacy) { block(SettingsSecurityRobot()) } } - fun labs(block: () -> Unit = {}) { - clickOnAndGoBack(R.string.room_settings_labs_pref_title) { block() } + fun labs(shouldGoBack: Boolean = true, block: () -> Unit = {}) { + if (shouldGoBack) { + clickOnAndGoBack(R.string.room_settings_labs_pref_title) { block() } + } else { + clickOn(R.string.room_settings_labs_pref_title) + block() + } } fun advancedSettings(block: SettingsAdvancedRobot.() -> Unit) { diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt new file mode 100644 index 0000000000..656201d812 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 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.app.ui.robot.settings.labs + +enum class LabFeature { + SWIPE_TO_REPLY, + TAB_UNREAD_NOTIFICATIONS, + LATEX_MATHEMATICS, + THREAD_MESSAGES, + AUTO_REPORT_ERRORS, + RENDER_USER_LOCATION +} From 985007a1c18241efb96b956a5b9e0f56ad70eeba Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Sat, 26 Feb 2022 10:46:51 +0000 Subject: [PATCH 0017/1430] Translated using Weblate (Czech) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 43 ++--------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 9d0220f3a6..cf022172c2 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -38,7 +38,6 @@ Telefonní číslo Pozvání do místnosti %1$s a %2$s - Prázdná místnost %s povýšili tuto místnost. %1$s zrušili pozvánku do místnosti pro %2$s @@ -260,7 +259,6 @@ Trvalý odkaz Zobrazit dešifrovaný zdroj Nahlásit obsah - Odhlásit Hlasový hovor Video hovor @@ -281,7 +279,6 @@ Pouze kontakty Matrix Žádné výsledky Místnosti - Komunity Odeslat záznamy Odeslat záznamy zřícení @@ -334,8 +331,6 @@ \nPřejete si nějaké přidat nyní\? Omlouváme se, ale nebyla nalezena žádná externí aplikace pro dokončení této akce. Šifrovaná zpráva - - Prosím, přečtěte si a souhlaste s pravidly tohoto serveru: Neobsahuje platný JSON Znovu požádat o šifrovací klíče z vašich ostatních relací. @@ -360,10 +355,7 @@ Hovor probíhá… Protější strana hovor nepřijala. Informace - - ${app_name} potřebuje oprávnění pro přístup k Vašemu mikrofonu pro uskutečnění hlasových hovorů. - ANO NE Pokračovat @@ -373,16 +365,11 @@ Odmítnout Zobrazit členy Přejít na nepřečtené - %d člen %d členové %d členů - - - - Opustit místnost Opravdu chcete opustit tuto místnost\? PŘÍMÉ KONVERZACE @@ -431,8 +418,6 @@ ${app_name} potřebuje oprávnění pro přístup k Vaší kameře a mikrofonu pro uskutečnění video hovoru. \n \nProsím, povolte přístup na následující hlášce abyste mohli uskutečnit hovor. - - Tuto změnu nelze zvrátit, protože povyšujete uživatele na stejnou úroveň, jakou máte vy. \nOpravdu to chcete udělat\? Toto by mohlo znamenat, že někdo škodlivě zachytává Vaši komunikaci nebo že Váš telefon nedůvěřuje certifikátu poskytnutému vzdáleným serverem. @@ -440,12 +425,9 @@ Certifikát se změnil z toho, kterému Váš telefon důvěřoval. Toto je VELMI NEOBVYKLÉ. Je doporučeno, abyste NEPŘIJALI tento nový certifikát. Certifikát se změnil z původně důvěryhodného na nyní nedůvěryhodný. Server patrně obnovil svůj certifikát. Kontaktujte administrátora kvůli očekávanému otisku. Přijměte certifikát pouze pokud administrátor serveru publikoval otisk, který odpovídá tomu uvedenému výše. - Vyhledat Filtrovat členy místnosti Žádné výsledky - - Všechny zprávy Přidat na domovskou obrazovku Obrázek profilu @@ -462,7 +444,6 @@ Označit za přečtené Žádný Zrušit - Přihlásit se se single sign-on To není platná adresa Matrix serveru Domovský server není dostupný na této adrese, zkontrolujte ji prosím @@ -553,7 +534,6 @@ Neobdržíte oznámení o příchozích zprávách, je-li aplikace na pozadí. Start při zavádění Čas požadavku na sync vypršel - Prodleva mezi jednotlivými syncy Verze Verze olm @@ -603,7 +583,6 @@ Deaktivovat můj účet Objevování Správa Vašich nastavení pro objevování. - Analýza Odeslat analytická data ${app_name} sbírá anonymní analytická data pro vylepšení aplikace. @@ -612,7 +591,6 @@ Aktualizovat veřejné jméno Viděn naposledy %1$s @ %2$s - Ověření Přihlášen jako Domovský server @@ -661,7 +639,6 @@ Toto jsou experimentální funkce, které mohou selhat neočekávanými způsoby. Použijte obezřetně. Nastavit jako hlavní adresu Odebrat jako hlavní adresu - Motiv vzhledu Chyba dešifrování Veřejné jméno @@ -672,7 +649,6 @@ Export klíčů do místního souboru Export Prosím, vytvořte frázi k zašifrování exportovaných klíčů. Pro import klíčů budete muset zadat stejnou přístupovou frázi. - Obnovení zašifrovaných zpráv Správa zálohy klíčů Import E2E klíčů místností @@ -721,7 +697,6 @@ Importovat e2e klíče ze souboru \"%1$s\". Potvrďte porovnáním následujícího s nastavením uživatele ve svých dalších relacích: Pokud se neshodují, zabezpečení Vaší komunikace může být ohroženo. - Vybrat adresář místností Název serveru Všechny místnosti na serveru %s @@ -731,7 +706,6 @@ %d nepřečtené oznámené zprávy %d nepřečtených oznámených zpráv - %d místnost %d místnosti @@ -834,8 +808,6 @@ Úvod Místnosti Pozvaní - - %2$s Vás vykopnul z %1$s %2$s Vám zakázal %1$s Důvod: %1$s @@ -899,7 +871,6 @@ Uložit klíč obnovy Sdílet Uložit jako soubor - Záloha již existuje na Vašem domovském serveru Vypadá to, že jste již nastavili zálohu klíče z jiné relace. Chcete ji nahradit zálohou, již právě provádíte\? Nahradit @@ -942,7 +913,6 @@ Kontroluji stav zálohy Smazat zálohu Smazat Vaše zálohované šifrovací klíče ze serveru\? Ke čtení šifrované historie zpráv již nebude moci použít klíč obnovy. - Nikdy neztraťte šifrované zprávy Použíjte zálohu klíče Nový bezpečný klíč zpráv @@ -950,11 +920,8 @@ Verze Algoritmus Podpis - Ověřeno! Rozumím - - Žádost na ověření %s chce ověřit Vaši relaci Neznámá chyba @@ -1107,7 +1074,6 @@ Obsah byl nahlášen jako nepatřičný. \n \nPokud si dále nepřejete vidět obsah tohoto uživatele, můžete jej ignorovat a tím skrýt jejich zprávy. - Ignorovat uživatele Všechny zprávy (hlučné) Všechny zprávy @@ -1295,7 +1261,6 @@ Ověřit %s Ověřeno %s Čekám na %s… - Zprávy v této místnosti nejsou koncově šifrovány. Zprávy v této místnosti jsou koncově šifrovány. \n @@ -1443,7 +1408,6 @@ Vytiskněte a uložte na bezpečném místě Uložte je na USB nebo zálohový disk Nahrajte do svého osobního úložiště v cloudu - Šifrování zapnuto Zprávy v této místnosti jsou koncově šifrovány. Zjistěte více a ověřte uživatele v jejich profilech. Šifrování není zapnuto @@ -1855,7 +1819,6 @@ Skrýt pokročilé Ukázat pokročilé %1$d z %2$d - Udělit souhlas Zrušit můj souhlas Udělili jste souhlas pro odeslání emailových adres a telefonních čísel na tento server pro identity za účelem nalezení dalších uživatelů podle svých kontaktů. @@ -1948,8 +1911,6 @@ Při přepojování hovoru došlo k chybě Připojit Nejprve se poraďte - - Probíhající hovor (%1$s) Při vyhledávání telefonního čísla došlo k chybě Číselník @@ -2149,7 +2110,6 @@ Zadejte název nového serveru, který chcete prozkoumat. Přidat nový server Váš server - Omlouváme se, došlo k chybě během pokusu o přistoupení: %s Adresa prostoru Prohlédnout a spravovat adresy tohoto prostoru. @@ -2354,7 +2314,6 @@ Otázka nebo téma hlasování Vytvořit hlasování Hlasování - Odeslat e-maily a telefonní čísla na %s Vaše kontakty jsou soukromé. Pro zjištění uživatelů z vašich kontaktů, potřebujeme vaše svolení k odeslání informací o kontaktech na váš server identit. Relace byla odhlášena! @@ -2505,4 +2464,6 @@ %1$d dalších Zobrazit méně + %1$s, %2$s a další + %1$s a %2$s \ No newline at end of file From f507c6c4a9a4e07a9a0760abbd7f32398d8c29b7 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 26 Feb 2022 10:36:50 +0000 Subject: [PATCH 0018/1430] Translated using Weblate (Hungarian) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 41 ++--------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 40121e2d1a..000da846b8 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -39,7 +39,6 @@ Meghívó egy szobába %1$s és %2$s Üres szoba - Induló szinkronizáció: \nFiók betöltése… Induló szinkronizáció: @@ -179,7 +178,6 @@ Törlés Átnevezés Tartalom Bejelentése - vagy Meghívás Kijelentkezés @@ -202,7 +200,6 @@ Csak Matrix névjegyek Nincs találat Szobák - Naplófájlok küldése Összeomlásnaplók küldése Képernyőkép küldése @@ -230,11 +227,9 @@ Ez nem tűnik érvényes e-mail címnek Ez az e-mail cím már használatban van. Elfelejtetted a jelszavad? - A Matrix-kiszolgáló szeretné ellenőrizni, hogy nem vagy robot Meg kell adnod a fiókodhoz tartozó e-mail-címet. Az e-mail-címed ellenőrzés sikertelen: győződj meg róla, hogy rákattintottál az e-mailben található hivatkozásra - Adj meg egy érvényes URL-t Hibás JSON Nem tartalmazott érvényes JSON-t @@ -250,14 +245,10 @@ Hívás folyamatban… A hívott fél nem vette fel. Információ - - A ${app_name}nek engedélyre van szüksége a mikrofon eléréséhez, hogy hanghívás tudjon indítani. - A ${app_name}nek engedélyre van szüksége a mikrofonod és kamerád eléréséhez, hogy videohívást tudj indítani. \n \nEngedélyezd a hozzáférést a következő felugró ablakon, hogy hívást tudj indítani. - IGEN NEM Folytatás @@ -265,7 +256,6 @@ Csatlakozás Elutasítás Ugrás az olvasatlanra - Szoba elhagyása Biztos el akarod hagyni a szobát? KÖZVETLEN CSEVEGÉSEK @@ -292,7 +282,6 @@ A tanúsítvány eltér attól, amit a telefonoddal megbízhatónak jelöltél. Ez RENDKÍVÜL SZOKATLAN. Javasoljuk, hogy NE FOGADD EL ezt az új tanúsítványt. Egy korábban megbízhatónak jelölt tanúsítvány megváltozott. Lehet, hogy a szerver frissítette a tanúsítványát. Lépj kapcsolatba a szerver adminisztrátorával és egyeztesd az ujjlenyomatot. Csak akkor fogadd el a tanúsítványt, ha a szerver adminisztrátortól kapott ujjlenyomat megegyezik a fentivel. - Keresés Szobatagok szűrése Nincs találat @@ -337,7 +326,6 @@ Nyilvános Név frissítése Legutóbb láttuk %1$s @ %2$s - Azonosítás Bejelentkezve mint Matrix szerver @@ -366,7 +354,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< Ezek kísérleti funkciók, ezek elromolhatnak nem számított módokon. Használd elővigyázatossággal. Fő címnek állítás Kiszedés fő címek közül - Visszafejtés hiba Nyilvános név Munkamenet-azonosító @@ -377,7 +364,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< Exportálás Írj be jelmondatot Ellenőrizd a jelmondatot - E2E szoba kulcsok importálása Szoba kulcsok importálása Kulcsok importálás helyi fájlból @@ -389,7 +375,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< Hitelesítés Hogy ellenőrizni lehessen, hogy ez a munkamenet megbízható, kérlek használj más kommunikáció módot a tulajdonossal (pl.: személyesen vagy telefonon keresztül) és kérdezd meg hogy a kulcs amit lát a Felhasználói Beállítások alatt megegyezik-e az alábbi kulccsal: Ha nem egyeznek, akkor a kommunikáció biztonsága kompromittálva lehet. A jövőben ez a hitelesítési mód kényelmesebbé lesz téve. - Válassz egy szoba könyvtárat Szerver neve Összes szoba a %s szerveren @@ -473,7 +458,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< %d tagság változás Tagok listázása - %d tag %d tag @@ -482,13 +466,10 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< %d új üzenet %d új üzenet - - %d olvasatlan üzenet %d olvasatlan üzenet - %d szoba %d szoba @@ -544,16 +525,10 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés A beszélgetés itt folytatódik Ez a szoba egy másik beszélgetés folytatása Régebbi üzenetek megjelenítéséhez kattints ide - - - - %d kiválasztva %d kiválasztva - - Rendszerriasztások vedd fel a kapcsolatot a szolgáltatás adminisztrátorával Ez a Matrix szerver túllépte valamely erőforrás korlátot így néhány felhasználó nem tud majd bejelentkezni. @@ -680,7 +655,6 @@ Helyezd biztonságba a kulcsokat, hogy ne vesszenek el. Kész Visszaállítási Kulcs mentése Mentés fájlba - Kérlek, készíts egy másolatot! Visszaállítási Kulcs megosztása… Visszaállítási Kulcs készítése jelmondatból, ez néhány másodpercet igénybe vehet. @@ -774,7 +748,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Üzenet küldése Enter billentyűvel Az Enter billentyű a virtuális billentyűzeten elküldi az üzenetet és nem új sort szúr be A jelszó nem érvényes - Média Alapértelmezett tömörítés Válassz @@ -811,8 +784,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Mellőz Ellenőrizve! Értem - - Ellenőrzési kérés %s szeretné ellenőrizni a munkamenetedet Ismeretlen Hiba @@ -909,7 +880,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nincs Visszavonás Bontás - Nem érhető el Matrix-kiszolgáló ezen a címen, ellenőrizd Háttér Szinkronizálási Mód Optimalizált akkumulátor használat @@ -920,7 +890,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze \nEz befolyásolja a rádió és az akkumulátor használatot, és folyamatosan egy értesítés fog megjelenni arról, hogy a ${app_name} figyel a neki küldött eseményekre. Nincs szinkroniziálás a háttérben Nem leszel értesítve az érkező üzenetekről, ha az alkalmazás csak a háttérben fut. - Felderítés Felderítési beállítások megváltoztatása. Nem használsz Azonosítási Szervert @@ -990,7 +959,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Ez a tartalom nem idevalónak lett bejelentve. \n \n Ha nem akarsz ettől a felhasználótól több üzenetet látni akkor blokkolhatod, hogy az üzenetei ne jelenjenek meg számodra. - Integrációk Botok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert. \n @@ -1207,7 +1175,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Ellenőrzés: %s Ellenőrizve: %s Várakozás %s felhasználóra… - Az üzenetek a szobában nincsenek végponttól végpontig titkosítva. A szobában az üzenetek végponttól végpontig titkosítva vannak. \n @@ -1353,7 +1320,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nyomtasd ki és tárold valahol biztonságos helyen Mentsd el egy USB kulcsra vagy mentő eszközre Másold fel a személyes felhő tárhelyedre - Titkosítás bekapcsolva Ebben a szobában az üzenetek végpontok között titkosítottak. További információkért és ellenőrzéshez nyisd meg a felhasználók profiljait! Titkosítás nincs engedélyezve @@ -1674,7 +1640,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze QR kód Meghívás QR kóddal A QR kód beolvasásához szükség van kamera hozzáférésre. - Elutasítás Fogadás Nincsen jogosultságod konferenciahívás indításához @@ -1939,8 +1904,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Átadás Kapcsolódás Először tájékozódj - - Aktív hívás (%1$s) A telefonszám megkeresésekor hiba történt Tárcsázó számlap @@ -2110,7 +2073,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Add meg a felfedezni kívánt új szerver nevét. Új szerver hozzáadása Matrix szervered - Bocsánat, hiba történt a csatlakozáskor ide: %s Tér cím Tér címek megjelenítése és kezelése. @@ -2298,7 +2260,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Matrix szerver kiválasztása A matrix szervert nem sikerül elérni ezen az URL-en: %s. Ellenőrizd a kapcsolatodat vagy add meg a matrix szervert kézzel. Értesítések figyelése - A névjegyeid személyes adatok. Ahhoz, hogy a névjegyzéked alapján megtalálhass felhasználókat, szükségünk van az engedélyedre, hogy a névjegy adatokat elküldhessük az azonosítási szolgáltatásnak. Legalább %1$s válasz szükséges @@ -2456,4 +2417,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze %d szerver jogosultság változott %d szerver jogosultság változott + %1$s, %2$s és még mások + %1$s és %2$s \ No newline at end of file From ff08ed322fa892a024c877c0f73c0c160f94fde0 Mon Sep 17 00:00:00 2001 From: Linerly Date: Sat, 26 Feb 2022 00:00:43 +0000 Subject: [PATCH 0019/1430] Translated using Weblate (Indonesian) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 45 +---------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 097efda7a6..d7856089e8 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -3,7 +3,6 @@ Undangan Ruangan %1$s dan %2$s Ruangan kosong - Pengaturan OK Batal @@ -85,9 +84,7 @@ Nama server Pilih direktori ruang Terverifikasi - Gandakan ke clipboard - Kirim tampilan layar Mohon uraikan kutu tersebut. Apa yang Anda lakukan\? Apa yang Anda harapkan terjadi\? Apa yang sebenarnya terjadi\? Catat dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk catat dan tangkapan layar, tidak akan terlihat secara umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silakan hapus centang: @@ -96,20 +93,15 @@ Kemajuan (%s%%) Nama Pengguna Nama pengguna dan/atau kata sandi salah - Anda perlu memasukkan alamat email yang tertaut pada akun. Verifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklik - JSON amburadul Tidak berisi JSON yang sah Pengajuan yang dikirimkan terlalu banyak Panggilan Video Masuk Panggilan Suara Masuk Panggilan Sedang Berlangsung… - - ${app_name} membutuhkan permisi atas akses mikrofon Anda untuk melakukan panggilan audio. - ${app_name} membutuhkan izin untuk mengakses kamera dan mikrofon Anda untuk melakukan panggilan video. \n \nHarap berikan akses pada halaman berikut ini untuk melakukan panggilan. @@ -142,19 +134,11 @@ %d perubahan keanggotaan Panggilan - - Daftar Anggota Arahkan ke pesan yang belum dibaca - - %d anggota - - - - Tinggalkan ruang Apa benar Anda ingin meninggalkan ruangan ini\? PERCAKAPAN LANGSUNG @@ -189,12 +173,9 @@ %d terpilih - Cari Saring anggota ruang Tidak ada hasil - - Semua pesan Tambahkan ke Layar Utama Gambar Profil @@ -263,8 +244,6 @@ Beranda Ruangan Telah Diundang - - Anda telah dikeluarkan dari %1$s oleh %2$s Anda telah dicekal dari %1$s oleh %2$s Alasan: %1$s @@ -303,13 +282,11 @@ Apabila cocok, tekan tombol verifikasi berikut. Apabila tidak, seseorang sedang menyadap perangkat ini dan mungkin perlu diblokir. Di masa mendatang proses verifikasi ini akan dimutakhirkan. - Semua ruangan dalam server %s Semua ruangan bawaan %s %d pesan pemberitahuan yang belum dibaca - Singkapan Riwayat Ruangan Siapa yang dapat membaca riwayat\? Siapapun @@ -323,7 +300,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Ini adalah fitur uji coba dan mungkin rusak tanpa terduga. Hati-hati menggunakannya. Tentukan sebagai alamat utama Tidak tentukan sebagai alamat utama - Tema Kesalahan dekripsi Nama perangkat @@ -335,7 +311,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Ekspor Masukkan kata sandi Tegaskan kata sandi - Mendengarkan peristiwa Pemberitahuan pihak ketiga Hak cipta @@ -371,7 +346,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Nama Perangkat Terakhir terlihat %1$s @ %2$s - Otentikasi Masuk sebagai Homeserver @@ -663,7 +637,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Gagal membuat koneksi real-time. \nSilakan minta administrator homeserver Anda untuk mengkonfigurasi server TURN agar panggilan untuk bekerja dengan andal. ${app_name} Panggilan Gagal - URL API homeserver Kirim riwayat permintaan pemberian kunci Space @@ -1011,7 +984,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Integrasi dinonaktifkan Pengelola integrasi Izinkan integrasi - Ini akan menggantikan Kunci atau Frasa Anda saat ini. Buat Kunci Keamanan baru atau atur Frasa Keamanan baru untuk cadangan yang ada. Lindungi dari kehilangan akses ke pesan & data terenkripsi dengan mencadangkan kunci enkripsi di server Anda. @@ -1032,7 +1004,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %d detik - Anda tidak akan diberitahu tentang pesan masuk saat aplikasi berada di latar belakang. Tidak ada sinkronisasi latar belakang ${app_name} akan disinkronkan di latar belakang secara berkala pada waktu yang tepat (dapat dikonfigurasi). @@ -1076,7 +1047,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Memutuskan sambungan dari server identitas Anda akan membuat Anda tidak dapat ditemukan oleh pengguna lain dan Anda tidak akan dapat mengundang orang lain melalui email atau nomor telepon. Kirim email dan nomor telepon Anda telah memberikan persetujuan untuk mengirim email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda. - Anda sedang berbagi email atau nomor telepon di server identitas %1$s. Anda harus menyambungkan kembali ke %2$s untuk berhenti membagikannya. Setujui Persyaratan Layanan server identitas (%s) agar Anda dapat ditemukan melalui email atau nomor telepon. Kami mengirimi Anda email konfirmasi ke %s, periksa email Anda dan klik tautan konfirmasi @@ -1147,7 +1117,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Semua pesan Semua pesan (brisik) Abaikan pengguna - Konten ini telah dilaporkan sebagai tidak pantas. \n \nJika Anda tidak ingin melihat konten dari pengguna ini, Anda dapat mengabaikan pengguna itu untuk menyembunyikan pesan dari pengguna. @@ -1295,11 +1264,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Kesalahan Tidak Diketahui %s ingin memverifikasi sesi Anda Permintaan Verifikasi - - Saya mengerti Terverifikasi! - Tanda Tangan Algoritma Versi @@ -1316,7 +1282,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Jangan kehilangan pesan terenkripsi Lindungi dari kehilangan akses ke pesan & data terenkripsi Cadangan Aman - Hapus kunci enkripsi yang sudah dicadangkan dari server\? Anda akan tidak dapat menggunakan kunci pemulihan untuk membaca riwayat pesan terenkripsi. Hapus Cadangan Memeriksa status cadangan @@ -1364,7 +1329,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Sepertinya Anda telah menyiapkan cadangan kunci dari sesi lain. Apakah Anda ingin menggantinya dengan yang Anda buat\? Cadangan sudah ada di homeserver Anda Kunci pemulihan telah disimpan. - Simpan sebagai File Bagikan Simpan Kunci Pemulihan @@ -1543,7 +1507,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka. Pesan ini tidak terenkripsi secara ujung-ke-ujung. Pesan di ruangan ini tidak terenkripsi secara ujung-ke-ujung. - Menunggu untuk %s… Diverifikasi %s Verifikasi %s @@ -1733,7 +1696,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tingkatkan Mohon sabar, ini mungkin membutuhkan waktu yang lama. Bergabung ke ruangan yang diganti - Ruangan Tanpa Nama Beberapa ruangan mungkin disembunyikan karena mereka privat dan Anda membutuhkan undangan. Beberapa ruangan mungkin disembunyikan karena mereka privat dan Anda membutuhkan undangan. @@ -2086,7 +2048,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Jika Anda batalkan, Anda mungkin kehilangan pesan terenkripsi dan data Anda jika Anda kehilangan akses ke login Anda. \n \nAnda juga dapat mengatur Cadangan Aman dan kelola kunci Anda di Pengaturan. - Salin ke penyimpanan awan pribadi Anda Simpan di flashdisk atau penyimpanan cadangan Cetak dan simpan di tempat yang aman @@ -2179,8 +2140,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %1$d panggilan aktif · - - Panggilan aktif (%1$s) Ada sebuah kesalahan saat mencari nomor telepon Tombol penyetel @@ -2275,7 +2234,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Pertanyaan atau topik poll Buat Poll Poll - Kirim email dan nomor telepon ke %s Kontak Anda privat. Untuk menemukan pengguna dari kontak Anda, kami membutuhkan izin untuk mengirim info kontak ke server identitas Anda. Sesinya telah dikeluarkan! @@ -2321,7 +2279,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. di sini Bantu kami mengidentifikasi masalah-masalah dan membuat ${app_name} lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda. \n -\n \nAnda dapat membaca semua kebijakan kami %s. Bantu buat ${app_name} lebih baik Aktifkan @@ -2415,4 +2372,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %d perubahan ACL server + %1$s, %2$s dan lainnya + %1$s dan %2$s \ No newline at end of file From 91418493a6501b115decb219cf80276662a44807 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sun, 27 Feb 2022 07:09:54 +0000 Subject: [PATCH 0020/1430] Translated using Weblate (Japanese) Currently translated at 97.4% (2103 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 138 +++++++++++++++------- 1 file changed, 97 insertions(+), 41 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index d249aeb745..0d35cc8c99 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -23,7 +23,6 @@ ルームへの招待 %1$sと%2$s 空のルーム - %1$sが今後のルーム履歴を%2$sに見えるように設定しました。 ルームのメンバー全員(招待された時点から) ルームのメンバー全員(参加した時点から) @@ -86,7 +85,6 @@ 招待中 低優先度 会話 - 不具合を報告 不具合の内容と状況の説明をお願いします。何をしましたか?何が起こるべきでしたか?実際に起こった事象は何でしょうか? ここに不具合の内容を記述 @@ -173,7 +171,6 @@ 最後のオンライン日時 %1$s @ %2$s 認証 - ログイン中のアカウント 言語を選択 言語 @@ -220,11 +217,9 @@ ルーム サインアウト 送信 - このホームサーバーは、あなたがロボットではないことの確認を求めています アカウントに登録されたメールアドレスの入力が必要です。 メールアドレスの確認に失敗しました:電子メールのリンクをクリックしたことを確認してください - 不正な形式のJSON 有効なJSONを含んでいませんでした ログイン要求が多すぎます @@ -256,7 +251,6 @@ %sの全てのメッセージを表示しますか? \n \nこの操作はアプリを再起動するため、時間がかかる場合があります。 - 外観 公開端末名 ルームのエンドツーエンド暗号鍵をエクスポート @@ -272,14 +266,10 @@ Matrixの連絡先のみ 通信先が通話の受取に失敗しました。 情報 - - ${app_name}は、音声通話を実行するためにマイクへアクセスするための許可を必要としています。 - ${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。 \n \n通話をするためには、次のポップアップでアクセスを許可してください。 - 発言を通報 写真を撮影 動画を撮影 @@ -289,7 +279,6 @@ リクエストの送信に失敗しました。 ウィジェットを作成できません。 ウィジェットをこのルームから削除してもよろしいですか? - 一致していない場合は、あなたのコミュニケーションの安全性が損なわれている可能性があります。 このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない。 認証済のセッションに対してのみ暗号化 @@ -306,7 +295,6 @@ 通知あり(音量大) 通知あり(サイレント) 不具合の報告 - このユーザーにあなたと同じ権限を与えます。この変更は取り消せません。 \nよろしいですか? 信用する @@ -318,7 +306,6 @@ 証明書はあなたの電話により信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書を承認しないことを強く推奨します。 証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。サーバーの管理者に連絡して、適切なフィンガープリントを確認してください。 サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。 - 検索 このアプリの情報をシステム設定で表示。 アプリの情報 @@ -355,7 +342,6 @@ ホーム画面にショートカットを作成 インラインURLプレビュー コミュニティーのアバター - 暗号鍵を要求している新しいセッション \'%s\' を追加しました。 未認証のセッション \'%s\' が暗号鍵を要求しています。 作成 @@ -370,15 +356,12 @@ %d個のメンバーシップの変更 メンバーを表示 - %d名のメンバー %d件の新しいメッセージ - - アバター スタンプを送る ダウンロード @@ -392,10 +375,6 @@ 申し訳ありません、この操作を完了するための外部アプリが見つかりません。 あなたの他のセッションに暗号鍵を再要求する。 鍵をこのセッションに送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。 - - - - %d個選択済 @@ -408,7 +387,6 @@ %d件の通知された未読メッセージ - %d個のルーム @@ -430,8 +408,6 @@ 表示するニックネームを変更 Markdown書式の入/切 Matrixアプリの管理を修正するには - - %1$sのホームサーバーの使用を継続するには、利用規約を確認し、同意する必要があります。 エラー 今すぐ確認 @@ -472,13 +448,11 @@ 会話から追放 鍵のバックアップ 鍵のバックアップを使用 - 詳細な通知設定 バックグラウンド同期モード バッテリーを考慮して最適化 リアルタイム性を重視して最適化 バックグラウンド同期を行わない - 入力中通知を送信 文字入力中であることを他のメンバーに伝えます。 開封確認メッセージを表示 @@ -732,7 +706,7 @@ QRコード QRコードによる追加 コードを共有 - ${app_name} で会話しましょう:%s + ${app_name}で話しましょう:%s 友達を招待 既知のユーザー 無効なQRコード(無効なURI)! @@ -812,7 +786,6 @@ ビデオ通話が行われています… 有効な認証情報がないため、権限がありません ${app_name} 呼び出し失敗 - ルームディレクトリの全てのルームを表示(露骨なコンテンツのあるルームを含む)する。 露骨なコンテンツのあるルームを表示 ルームディレクトリ @@ -970,7 +943,6 @@ %1$sがルームのアバターを削除しました ルームの説明を削除しました ルーム名を削除しました - ディスカバリー設定を管理します。 ディスカバリー(発見) これにより、現在のキーまたはフレーズが置き換えられます。 @@ -1101,7 +1073,6 @@ 鍵のバックアップで管理 鍵のバックアップを使用 暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう - バックアップされた暗号鍵をサーバーから削除しますか?今後、現在のリカバリーキーを使って、暗号化されたメッセージの履歴を読むことができなくなります。 バックアップを削除 バックアップの状態を確認しています @@ -1151,7 +1122,6 @@ 別のセッションで鍵のバックアップを既に設定しているようです。上書きしますか? ホームサーバーにバックアップが存在しています リカバリーキーが保存されました。 - リカバリーキーを保存 コピーをしました リカバリーキーはパスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください @@ -1363,11 +1333,8 @@ 不明なエラー このルームを含む参加済のスペース このルームにアクセスできるスペースを決定します。スペースが選択されると、そのメンバーはルーム名を見つけて参加できます。 - - 了解 完了しました! - メッセージの新しい鍵 暗号化されたメッセージを決して失わないために セキュアバックアップ @@ -1418,7 +1385,6 @@ この操作を実行するための権限がありません。システム設定から権限を付与してください。 IDサーバーに接続できませんでした IDサーバーのURLを入力 - 同意する 同意を撤回 あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。 @@ -1642,7 +1608,7 @@ あなたの非公開スペース あなたの公開スペース 自分のみ - スレッドでディスカッションを整理して管理 + スレッドで議論を整理して管理 %sを待機しています… この端末でスキャン 認証を送信済 @@ -1744,7 +1710,6 @@ このファイルは大きすぎてアップロードできません。 この情報の送信に同意しますか? 連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 - メールアドレスと電話番号を%sに送信 このIDサーバーはポリシーを提供していません IDサーバーのポリシーを隠す @@ -1886,7 +1851,6 @@ 自分の会話は、自分のもの。 %1$sがこのルームを「招待者のみ参加可能」に設定しました。 選択したメッセージをネタバレとして送信 - %1$sはこのルームを「リンクを知っている人が参加可能」に設定しました。 初めに設定画面でIDサーバーの利用規約を承認してください。 初めにIDサーバーを設定してください。 @@ -1910,7 +1874,7 @@ \nアップグレードは通常、ルームがサーバー上で処理される仕方にだけ影響します。 一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。 %sして、このルームを皆に紹介しましょう。 - このコードを皆と共有し、スキャンして追加してもらい、会話を始めましょう。 + このコードを共有し、スキャンして追加してもらい、会話を始めましょう。 正当な参加者が%sにアクセスできることを確認してください。 参加者を追加 @@ -2183,7 +2147,6 @@ 暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。 詳しく知る セキュリティーを高めるために、使い捨てコードが一致しているのを確認して、%sを検証しましょう。 - 暗号化の設定が正しくありません。 暗号化を復元 暗号化を有効な状態に取り戻すために、管理者に連絡してください。 @@ -2228,7 +2191,7 @@ %1$sの権限レベルを変更しました。 誰と使いますか? どんなスペースを作りますか? - あなたとチームメイトの非公開のスペース + 自分と仲間の非公開のスペース ルームを整理するためのプライベートスペース ここが会話のスタート地点です。 ここが%sのスタート地点です。 @@ -2266,4 +2229,97 @@ 全てリセット 連絡先 検証がキャンセルされました。再び検証を開始することができます。 + 押し続けて録音し、離すと送信 + セキュリティー向上のため、PINコードを選択してください + + %d個のサーバーアクセス制御リストの変更 + + 置き換えられたルームに参加 + このルームが発見できません。存在することを確認してください。 + 指紋や顔画像など、端末に固有の生体認証を有効にしてください。 + 絵文字で検証 + テキストで検証 + すべてのセッションを検証し、アカウントとメッセージが安全であることを確認してください + ログインしている場所を確認 + 復旧用の手段を全て無くしてしまいましたか?全てリセットする + 、あるいはクロス署名に対応した他のMatrixのクライアント + どのような議論を%sで行いたいですか? + クロス署名の設定に失敗しました + 履歴とメッセージが消去され、信頼済の端末、信頼済のユーザーが取り消されます + 全てをリセットすると + 最新の${app_name}を他の端末で、${app_name} ウェブ版、${app_name} デスクトップ版、${app_name} iOS、${app_name} Android、あるいはクロス署名に対応した他のMatrixのクライアントでご使用ください + スライドして通話を終了 + 電話番号を検索する際にエラーが発生しました + 着信を拒否しました + それぞれにルームを作りましょう。後から追加することもできます(既にあるルームも追加できます)。 + 分かるように特徴を記入してください。これはいつでも変更できます。 + 目立つように特徴を記入してください。これはいつでも変更できます。 + 未読のメッセージ数のみを通知に表示。 + 2分間${app_name}を使用しないと、PINコードが要求されます。 + 🔐️ ${app_name}で話しましょう + 個人情報保護の観点から、${app_name}はハッシュ化されたメールアドレスと電話番号の送信のみをサポートしています。 + アプリの名前を変更しました!アプリは最新版で、アカウントにはログイン済です。 + ステートイベントを送信 + ステートイベント + カスタムのステートイベントを送信 + ステートイベントを送信しました! + 続行するには名前を付けてください。 + どんな作業に取り組みますか? + + あと%1$d件 + + Matrix上の連絡先を検索 + 通話の転送中にエラーが発生しました + 2分後にPINコードを要求 + ルーム名やメッセージの内容などの詳細を表示。 + エラーが多すぎます。ログアウトしました + 警告!もう一度誤ったコードを入力すると、ログアウトします! + + コードが誤っています。残りの試行回数は%d回です + + プッシュ通知を有効にするには、設定を確認してください + リンク %1$s は別のサイトに移動します:%2$s +\n +\n続行してよろしいですか? + このリンクを再確認してください + ログインを検証してください:%1$s + 機密ストレージのアクセスに失敗しました + この設定を有効にすると、全てのアクティビティーにFLAG_SECUREを追加します。変更を有効にするにはアプリケーションの再起動が必要です。 + このアカウントは無効化されています。 + 個人のクラウドストレージにコピーしましょう + 印刷して安全な場所に保管しましょう + %2$sと%1$sが設定されました。 +\n +\n安全な場所で保管してください!それらは、アクティブなセッションを全て失ってしまった際、暗号化されたメッセージや安全な情報のロックを解除するために必要となります。 + 作成したアイデンティティーキーを公開しています + アプリケーションのスクリーンショットを防ぐ + 続行するには%1$sか%2$sを使用してください。 + エラーのためメッセージが送信されませんでした + %sにいない人を探していますか? + 直接${app_name}で招待を受け取るには、設定画面から%sしてください。 + PINコードを入力しなければ${app_name}のロックを解除することはできません。 + ${app_name}を開く際にはPINコードの入力が必要です。 + あなたがブロックされているルームを開くことはできません。 + PINコードの検証に失敗しました。新しいコードを入力してください。 + 端末の連絡先がありません + 暗号化の履歴を待機しています + 送信者があなたのセッションを信頼していないため、このメッセージにアクセスすることができません + 送信者によりブロックされているため、このメッセージにアクセスすることができません + このメッセージを待機しています。時間がかかる可能性があります + ルームの設定の変更に成功しました + 確認のため、セキュリティーフレーズを再入力してください。 + ホームサーバー(%1$s)が、IDサーバーに%2$sを設定するよう提案しています + IDサーバー %s から切断しますか? + ダイレクトメッセージを作成できませんでした。招待したユーザーを確認し、もう一度やり直してください。 + セキュリティーフレーズ + 自分と仲間 + メッセージの種類がありません + 絵文字の一覧を閉じる + 絵文字の一覧を開く + 認証に失敗しました + 復旧を設定しています。 + このセッションは、他のセッションと検証を共有することができません。 +\n検証は端末に保存され、新しいバージョンのアプリで共有されます。 + %1$s、%2$s他 + %1$sと%2$s \ No newline at end of file From a4d9b4d5a80046d35ee7e71cb24aee3e14059b34 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Sat, 26 Feb 2022 00:11:55 +0000 Subject: [PATCH 0021/1430] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 43 +------------------ 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index b603364d4e..b9e2fa3791 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -39,7 +39,6 @@ Convite de Sala %1$s e %2$s Sala vazia - Seu convite %1$s criou a sala Você criou a sala @@ -244,7 +243,6 @@ Deletar Renomear Reportar Conteúdo - ou Convidar Fazer signout @@ -267,7 +265,6 @@ Contatos de Matrix somente Nenhum resultado Salas - Enviar logs Enviar crash logs Enviar screenshot @@ -295,11 +292,9 @@ Isto não parece com um endereço de email válido Este endereço de email já está definido. Esqueceu senha\? - Este servidorcasa gostaria de assegurar que você não é um robô O endereço de email linkado a sua conta deve ser entrado. Falha para verificar endereço de email: assegure-se que clicou no link no email - Por favor entre um URL válido JSON malformado Não continha JSON válido @@ -315,14 +310,10 @@ Chamada Em Progresso… O lado remoto falhou para atender. Informação - - ${app_name} precisa de permissão para acessar seu microfone para performar chamadas de áudio. - ${app_name} precisa de permissão para acessar sua câmera e seu microfone para performar chamadas de vídeo. \n \nPor favor permita acesso no próximo pop-up para ser capaz de fazer a chamada. - SIM NÃO Continuar @@ -330,7 +321,6 @@ Juntar-se Rejeitar Pular para não-lida(s) - Sair de sala Você tem certeza que você quer sair da sala\? Mensagens Diretas @@ -357,7 +347,6 @@ O certificado tem mudado de um que era confiado por seu telefone. Isto é ALTAMENTE INCOMUM. É recomendado que você NÃO ACEITE este novo certificado. O certificado tem mudado de um previamente confiado para um que não é confiado. O servidor pode ter renovado seu certificado. Contacte o/a administrador(a) de servidor para a impressão digital esperada. Somente aceite o certificado se o/a administrador(a) de servidor tem publicado uma impressão digital que corresponde com a acima. - Pesquisar Filtrar membros de sala Nenhum resultado @@ -402,7 +391,6 @@ Atualizar Nome Público Visto por último %1$s @ %2$s - Autenticação Feito login como Servidorcasa @@ -433,7 +421,6 @@ Estes são recursos experimentais que podem quebrar de maneiras inesperadas. Use com cuidado. Definir como endereço principal Des-definir como endereço principal - Erro de decriptação Nome público ID de sessão @@ -444,7 +431,6 @@ Exportar Entrar frasepasse Confirmar frasepasse - Importar chaves de sala E2E Importar chaves de sala Importar as chaves de um arquivo local @@ -456,7 +442,6 @@ Verificar Confirme ao comparar o seguinte com as Configurações de Usuária(o) em sua outra sessão: Se não correspondem, a segurança de sua comunicação pode estar comprometida. - Selecionar um diretório de salas Nome de servidor Todas as salas em servidor %s @@ -532,7 +517,6 @@ Você foi expulsa(o) de %1$s por %2$s Você foi banida(o) de %1$s por %2$s Razão: %1$s - %d membro %d membros @@ -541,8 +525,6 @@ %d nova mensagem %d novas mensagens - - %d mudança de filiação %d mudanças de filiação @@ -552,7 +534,6 @@ %d mensagem notificada não-lida %d mensagens notificadas não-lidas - %d sala %d salas @@ -576,10 +557,6 @@ Desculpe, nenhum aplicativo externo tem sido encontrado para completar esta ação. Re-requisitar chaves de encriptação de suas outras sessões. Por favor lance ${app_name} num outro dispositivo que possa decriptar a mensagem para que ele possa enviar as chaves para esta sessão. - - - - %d selecionada %d selecionadas @@ -603,8 +580,6 @@ Muda seu apelido de exibição Ativar/Desativar markdown Para consertar gerenciamento de Apps Matrix - - Para continuar usando o servidorcasa %1$s você deve revisar e aceitar os termos e condições. Revisar agora Desativar Conta @@ -690,7 +665,6 @@ Convites, remoções e bans são desafetados. Mostrar eventos de conta Inclui mudanças de avatar e nome de exibição. - Restrições de background estão desabilitadas para ${app_name}. Este teste devia ser rodado usando dados móveis (sem Wi-Fi). \n%1$s Restrições de background estão habilitadas para ${app_name}. @@ -757,7 +731,6 @@ Copiar Sucesso Notificações - Chamada ${app_name} Falhou Falha para estabelecer conexão em tempo real. \nPor favor peça ao/à administrador(a) de seu servidorcasa para configurar um servidor TURN a fim que chamadas funcionem confiavelmente. @@ -805,7 +778,6 @@ \nIsto vai impactar uso de rádio e bateria, vai ter uma notificação permanente exibida declarando que ${app_name} está à escuta por eventos. Sem sinc em background Você não vai ser notificada(o) sobre mensagens entrantes quando o app está em background. - Integrações Use um gerenciador de integrações para gerenciar bots, bridges, widgets e pacotes de stickers. \nGerenciadores de integrações recebem dados de configuração, e podem modificar widgets, enviar convites de sala e definir níveis de poder em seu nome. @@ -921,7 +893,6 @@ Salvar Chave de Recuperação Compartilhar Salvar como Arquivo - A chave de recuperação tem sido salva. Um backup já existe em seu servidorcasa Parece que você já tem configurado backup de chave de uma outra sessão. Você quer substituí-lo pelo que você está criando\? @@ -975,7 +946,6 @@ Checando estado de backup Deletar Backup Deletar suas chaves de encriptação, das quais foi feito backup, do servidor\? Você não vai ser mais capaz de usar sua chave de recuperação para ler histórico de mensagens encriptadas. - Backup Seguro Salvaguardar-se contra perda de acesso a mensagens & dados encriptados Nunca perca mensagens encriptadas @@ -991,11 +961,8 @@ Versão Algoritmo - Verificada(o)! Entendido - - Requisição de Verificação %s quer verificar sua sessão Erro Desconhecido @@ -1159,7 +1126,6 @@ Este conteúdo foi reportado como inapropriado. \n \nSe você não quer ver mais nada de conteúdo desta(e) usuária(o), você pode ignorá-la(o) para esconder mensagens dela(e). - Ignorar usuária(o) Todas as mensagens (barulhento) Todas as mensagens @@ -1363,7 +1329,6 @@ Verificar %s Verificou %s Esperando por %s… - Mensagens nesta sala não são encriptadas ponta-a-ponta. Mensagens nesta sala são encriptadas ponta-a-ponta. \n @@ -1521,7 +1486,6 @@ Mensagem… Usar Arquivo Checando Chave de backup - Se você cancelar agora, você pode perder mensagens & dados encriptados se você perder acesso a seus logins. \n \nVocê também pode configurar Backup Seguro & gerenciar suas chaves em Configurações. @@ -1823,7 +1787,6 @@ Você poderia habilitar isto se a sala vai somente ser usada para colaborar com times internos em seu servidorcasa. Isto não poder ser mudado mais tarde. Bloquear qualquer pessoa que não é parte de %s de jamais se juntar a esta sala %1$d de %2$d - Dar consentimento Revogar meu consentimento Você tem dado seu consentimento para enviar emails e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos. @@ -1913,8 +1876,6 @@ Transferir Conectar Consultar primeiro - - Chamada ativa (%1$s) Houve um erro ao procurar o número de telefone Pad de disco @@ -2112,7 +2073,6 @@ Enviar vídeo com o tamanho original Enviar vídeos com o tamanho original - Desculpe, um erro ocorreu enquanto tentando se juntar: %s Endereço de espaço Ver e gerenciar endereços deste espaço. @@ -2313,7 +2273,6 @@ Sondar pergunta ou tópico Criar Sondagem Sondagem - Enviar emails e números de telefone para %s Seus contatos são privados. Para descobrir usuárias(os) de seus contatos, você precisa de permissão para enviar info de contato a seu servidor de identidade. O signout desta sessão tem sido feito! @@ -2458,4 +2417,6 @@ %d mudança de ACLs de servidor %d mudanças de ACLs de servidor + %1$s, %2$s e outras(os) + %1$s e %2$s \ No newline at end of file From 8597d1144239ea11ed55fb3f8bb40f02274e8b20 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Fri, 25 Feb 2022 19:15:18 +0000 Subject: [PATCH 0022/1430] Translated using Weblate (Slovak) Currently translated at 98.8% (2132 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- vector/src/main/res/values-sk/strings.xml | 43 ++--------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index d9d9c7b1cd..18af56a598 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -39,7 +39,6 @@ Pozvanie do miestnosti %1$s a %2$s Prázdna miestnosť - %s aktualizoval/a túto miestnosť. Úvodná synchronizácia: \nPrebieha import účtu… @@ -197,7 +196,6 @@ Vymazať Premenovať Nahlásiť obsah - alebo Pozvať Odhlásiť sa @@ -220,7 +218,6 @@ Len Matrix kontakty Žiadne výsledky Miestnosti - Odoslať záznamy Odoslať záznamy o zlyhaní Odoslať snímku obrazovky @@ -248,11 +245,9 @@ Zdá sa, že toto nie je platná emailová adresa Táto emailová adresa sa už používa. Zabudli ste heslo? - Tento domovský server by sa rád uistil, že nieste robot Musíte zadať emailovú adresu prepojenú s vašim účtom. Nepodarilo sa overiť emailovú adresu: Uistite sa, že ste správne klikli na odkaz v emailovej správe - Zadajte platnú adresu URL Chybné údaje vo formáte JSON Neplatné údaje vo formáte JSON @@ -269,14 +264,10 @@ Prebiehajúci hovor… Vzdialenej strane sa nepodarilo prijať hovor. Informácia - - Aby ste mohli uskutočňovať audio hovory, ${app_name} potrebuje prístup k mikrofónu vašeho zariadenia. - ${app_name} potrebuje povolenie na prístup k vašej kamere a mikrofónu na uskutočňovanie videohovorov. \n \nPovoľte prístup v ďalších vyskakovacích oknách, aby ste mohli uskutočniť hovor. - ÁNO NIE Pokračovať @@ -284,7 +275,6 @@ Vstúpiť Odmietnuť Preskočiť na neprečítanú správu - Opustiť miestnosť Ste si istí, že chcete opustiť miestnosť? PRIAME KONVERZÁCIE @@ -311,7 +301,6 @@ Certifikát sa zmenil na iný, ktorému tento telefón dôveroval. Toto je VEĽMI NEZVYČAJNÉ. Odporúča sa, aby ste tento nový certifikát NEPRIJALI. Certifikát sa zmenil z predtým dôveryhodného na nedôveryhodný. Server mohol obnoviť svoj certifikát. Obráťte sa na správcu servera, aby vám poskytol očakávaný odtlačok. Dôverujte certifikátu len v prípade, že správca servera zverejnil odtlačok zhodný s odtlačkom zobrazeným vyššie. - Hľadať Filtrovať členov v miestnosti Žiadne výsledky @@ -364,7 +353,6 @@ Aktualizovať verejné meno Naposledy videné %1$s @ %2$s - Overenie Prihlásený ako Domovský server @@ -402,7 +390,6 @@ Tieto funkcie sú experimentálne a môžu sa nečakane pokaziť. Pri používaní buďte opatrní. Nastaviť ako hlavnú adresu Zrušiť nastavenie ako hlavnej adresy - Vzhľad Chyba dešifrovania Verejné meno @@ -414,7 +401,6 @@ Exportovať Zadajte prístupovú frázu Potvrďte prístupovú frázu - Importovať šifrovacie kľúče miestnosti Importovať kľúče miestnosti Importovať kľúče z lokálneho súboru @@ -426,7 +412,6 @@ Overiť Ak chcete overiť, či táto relácia je skutočne dôveryhodná, kontaktujte jeho vlastníka iným spôsobom (napr. osobne alebo cez telefón) a opýtajte sa ho, či kľúč, ktorý má zobrazený v Nastaveniach, sa zhoduje s kľúčom zobrazeným nižšie: Ak sa kľúče zhodujú, stlačte tlačidlo Overiť nižšie. Ak sa nezhodujú, niekto ďalší odpočúva toto zariadenie a mali by ste ho pridať na čiernu listinu. - Vyberte adresár miestností Názov servera Všetky miestnosti na serveri %s @@ -488,7 +473,6 @@ %d zmien členstva Zobraziť členov - 1 člen %d členovia @@ -499,14 +483,11 @@ %d nové správy %d nových správ - - 1 neprečítaná správa %d neprečítané správy %d neprečítaných správ - 1 miestnosť %d miestnosti @@ -546,10 +527,6 @@ Nebola nájdená žiadna vhodná aplikácia na dokončenie tejto akcie. Prosím, vložte svoje heslo. Ak je to možné, prosím popis napíšte v angličtine. - - - - 1 vybratý %d vybratí @@ -569,8 +546,6 @@ Mení vaše zobrazované meno / prezývku Zapnutie/vypnutie formátovanie textu markdown Užitočné na opravu spravovania Matrix aplikácií - - Táto miestnosť bola nahradená inou a nie je viac aktívna. Konverzácia pokračuje tu Táto miestnosť je pokračovaním predchádzajúcej konverzácii @@ -662,7 +637,6 @@ Pozvania, odstránenia a zákazy nie sú ovplyvnené. Zobrazovať udalosti účtu Zahŕňa zmeny zobrazovaného mena a obrázka v profile. - Heslo Spustiť predvolený fotoaparát v systéme namiesto zobrazenia vlastnej vstavanej obrazovky. Príkaz \"%s\" vyžaduje viac argumentov, alebo nie sú všetky zadané správne. @@ -735,7 +709,6 @@ Zrušiť Odpojiť sa Odmietnuť - Toto nie je platná adresa Matrix serveru Domovský server je nedostupný na tejto URL adrese, preverte to prosím Relácie @@ -799,7 +772,6 @@ \nBude to mať vplyv na používanie rádia a batérie, bude sa zobrazovať trvalé oznámenie, že ${app_name} počúva udalosti. Žiadna synchronizácia na pozadí Nebudete dostávať oznámenia o prichádzajúcich správach, keď aplikácia pracuje na pozadí. - Integrácie Použite správcu integrácií na nastavenie botov, premostení, widgetov a balíčkov s nálepkami. \nSprávcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení. @@ -897,7 +869,6 @@ Uložiť kľúč obnovenia Zdieľať Uložiť ako súbor - Kľúč obnovenia bol uložený. Záloha už existuje na vašom domovskom serveri Zdá sa, že ste si už zálohovanie kľúčov nastavili z inej relácie. Chcete ho nahradiť zálohou, ktorú vytvárate teraz\? @@ -953,7 +924,6 @@ Zisťovanie stavu zálohovania Vymazať zálohu Vymazať vaše zálohované šifrovacie kľúče z domovského servera\? Na čítanie histórie zašifrovaných správ už nebudete môcť použiť kľúč na obnovenie. - Bezpečné zálohovanie Zabezpečte sa proti strate šifrovaných správ a údajov Nikdy neprídete o šifrované správy @@ -971,11 +941,8 @@ Verzia Algoritmus Podpis - Overené! Rozumiem - - Žiadosť o overenie %s chce overiť vašu reláciu Neznáma chyba @@ -1381,7 +1348,6 @@ Nastavenia miestnosti Verzia miestnosti Nová hodnota - Číselník Zavolať späť Vyčistiť históriu @@ -1680,7 +1646,6 @@ Otvoriť ponuku vytvorenia miestnosti Zdá sa, že serveru trvá príliš dlho, kým odpovie, čo môže byť spôsobené buď zlým pripojením, alebo chybou servera. Skúste to o chvíľu znova. Súhlasíte so zaslaním týchto informácií\? - Preskočiť na potvrdenie o prečítaní Vlastné (%1$d) v %2$s Predvolené v %1$s @@ -1783,7 +1748,6 @@ Žiadna odpoveď Podržali ste hovor Automaticky aktualizovať nadradený priestor - Niektoré miestnosti môžu byť skryté, pretože sú súkromné a potrebujete pozvánku. Tento priestor nemá žiadne miestnosti Pre viac informácií sa obráťte na správcu domovského servera @@ -1906,7 +1870,6 @@ Zmienky a kľúčové slová Iba zmienky a kľúčové slová Medzinárodné telefónne čísla musia začínať znakom \"+\" - Ak chcete zistiť existujúce kontakty, potrebujete odoslať kontaktné informácie (e-maily a telefónne čísla) na server totožností. Pred odoslaním vaše údaje zahašujeme kvôli ochrane osobných údajov. Odoslať e-maily a telefónne čísla na %s Dali ste súhlas na odosielanie e-mailov a telefónnych čísel na tento server totožností na objavenie ďalších používateľov z vašich kontaktov. @@ -1965,7 +1928,6 @@ \n \nZastaviť proces zmeny hesla\? Zabudli ste alebo ste stratili všetky možnosti obnovy\? Obnovte všetko - Použite prístupovú frázu na obnovenie alebo kľúč Použite najnovšiu aplikáciu ${app_name} na svojich ostatných zariadeniach: Použite najnovšiu aplikáciu ${app_name} na svojich ostatných zariadeniach, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} pre Android alebo iného klienta Matrix podporujúceho krížové podpisovanie @@ -1994,7 +1956,6 @@ %1$d aktívne hovory · %1$d aktívnych hovorov · - Prebiehajúci hovor (%1$s) Pri vyhľadávaní telefónneho čísla došlo k chybe Žiadna odpoveď @@ -2164,7 +2125,6 @@ Táto relácia nemôže zdieľať toto overenie s vašimi ostatnými reláciami. \nOverenie bude uložené lokálne a zdieľané v budúcej verzii aplikácie. Akcie správcu - Zobrazujú sa len prvé výsledky, zadajte ďalšie písmená… Zatraste telefónom a otestujte prah detekcie Prahová hodnota detekcie @@ -2184,7 +2144,6 @@ Zadajte kľúčové slová pre vyhľadanie reakcie. Odobrať z nízkej priority Pridať k nízkej priorite - Na pokračovanie použite %1$s alebo %2$s. Ak chcete pokračovať, zadajte prístupovú frázu pre zálohovanie kľúčov. Generovanie kľúča SSSS z prístupovej frázy %s @@ -2476,4 +2435,6 @@ %d zmeny ACL servera %d zmien ACL servera + %1$s, %2$s a ďalší + %1$s a %2$s \ No newline at end of file From 8c6b15a1ede171c2438f37b56f0d674914e07454 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 25 Feb 2022 20:26:59 +0000 Subject: [PATCH 0023/1430] Translated using Weblate (Swedish) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 57 ++++++----------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 38f7a60928..69c25ebba3 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -88,7 +88,6 @@ Telefonnummer Rumsinbjudan %1$s och %2$s - Tomt rum Inledande synk: \nImporterar konto… @@ -264,7 +263,6 @@ Återkalla Koppla ifrån Rapportera innehåll - eller Bjud in Godkänn @@ -296,7 +294,6 @@ Bara Matrix-kontakter Inga resultat Rum - Gemenskaper Skicka loggar Skicka kraschloggar @@ -322,7 +319,6 @@ Skicka röst Är du säker på att du vill starta ett röstsamtal\? Är du säker på att du vill skapa ett videosamtal\? - Skicka filer Skicka dekal Ta foto eller video @@ -339,11 +335,9 @@ Det här ser inte ut som en giltig e-postadress Den här e-postadressen är redan definierad. Glömt lösenordet\? - Denna hemserver skulle vilja verifiera att du inte är en robot Du måste skriva in e-postadressen länkad till ditt konto. Misslyckades att verifiera e-postadressen: se till att du klickade på länken i e-brevet - Vänligen granska och acceptera villkoren för denna hemserver: Vänligen skriv in en giltig URL Det här är inte en giltig Matrixserveradress @@ -374,14 +368,10 @@ Videosamtal pågår… Den andra parten svarade inte. Information - - ${app_name} behöver tillstånd att komma åt din mikrofon för hålla röstsamtal. - ${app_name} behöver tillstånd att komma åt din kamera och mikrofon för att kunna utföra videosamtal. \n \nVänligen ge tillstånd i nästa popup för att kunna utföra samtalet. - JA NEJ Fortsätt @@ -390,16 +380,10 @@ Avslå Lista medlemmar Hoppa till oläst - - %d medlem %d medlemmar - - - - Lämna rum Är du säker på att du vill lämna rummet\? DIREKTCHATT @@ -409,8 +393,6 @@ Kicka Ignorera Filtrera rumsmedlemmar - - Fäst rum med missade aviseringar Fäst rum med olästa meddelanden Alla rum på %s-servern @@ -420,7 +402,6 @@ %d rum Rum - Detta kommer att göra ditt konto permanent oanvändbart. Du kommer inte kunna logga in, och ingen kommer kunna registrera sig med samma användar-ID. Detta kommer att få ditt konto att lämna alla rum det är med i, och det kommer att ta bort din kontoinformation från din identitetsserver. Den här handlingen går inte att ångra. \n \nAtt inaktivera ditt konto får oss normalt inte att glömma meddelanden du har skickat. Om du skulle vilja att vi glömmer dina meddelanden, markera rutan nedan. @@ -456,7 +437,6 @@ Meddelanden som innehåller mitt visningsnamn Användarinställningar Innehåller byten av avatar eller visningsnamn. - Lösenord Byt lösenord Nuvarande lösenord @@ -473,7 +453,6 @@ Din återställningsnyckel är ett skyddsnät - du kan använda den för att återfå åtkomst till dina krypterade meddelanden om du skulle glömma din lösenfras. \nLagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp) Lagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp) - Byt nätverk Alla gemenskaper Allmänt @@ -483,7 +462,6 @@ Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera användare i deras profiler. Avignorera Kunde inte verifiera den externa serverns identitet. - Alla meddelanden Lägg till e-postadress Lägg till telefonnummer @@ -541,7 +519,6 @@ Inaktivera mitt konto Upptäckbarhet Hantera dina upptäckbarhetsinställningar. - Inloggad som Hemserver Identitetsserver @@ -714,7 +691,6 @@ Använd den här sessionen för att verifiera din nya och ge den tillgång till krypterade meddelanden. Om du avbryter så kommer du inte kunna läsa krypterade meddelanden på den här enheten, och andra användare kommer inte att lita på den Om du avbryter så kommer du inte kunna läsa krypterade meddelanden på din nya enhet, och andra användare kommer inte att lita på den - När jag bjuds in till ett rum Samtalsinbjudningar Meddelanden skickade av en bott @@ -755,15 +731,12 @@ Integrationer är avstängda Aktivera \'Tillåt integrationer\' I inställningarna för att göra detta. Exportera nycklarna till en lokal fil - Importera nycklarna från en lokal fil - Välj en rumskatalog %d oläst aviserat meddelande %d olästa aviserade meddelanden - %1$s: %2$d meddelande %1$s: %2$d meddelanden @@ -783,7 +756,6 @@ Krypterat meddelande kontakta din tjänstadministratör Spara som fil - Radera dina säkerhetskopierade krypteringsnycklar från servern\? Du kommer inte längre kunna använda din återställningsnyckel för att läsa krypterad meddelandehistorik. Skydda dig mot att tappa åtkomst till krypterade meddelanden och data Nya säkra meddelandenycklar @@ -1013,7 +985,6 @@ Sätt upp säker säkerhetskopiering Alla nycklar säkerhetskopierade Algoritm - %s vill verifiera din session Visa borttagna meddelanden Visa en platshållare för borttagna meddelanden @@ -1029,7 +1000,6 @@ %1$s, %2$s och %3$d annan har läst %1$s, %2$s och %3$d andra har läst - Du ignorerar inga användare Annan Om du har skapat ett konto på en hemserver så kan du använda ditt Matrix-ID (t.ex. @användare:domän.com) och lösenord nedan. @@ -1124,7 +1094,6 @@ Du kommer inte att bli aviserad om inkommande meddelanden när appen är i bakgrunden. Starta vid boot Timeout för synkbegäran - Fördröjning mellan varje synkronisering Lokala kontakter Kontaktbehörighet @@ -1172,7 +1141,6 @@ Rummets interna ID Sätt som huvudadress Avsätt som huvudadress - Avkrypteringsfel Publikt namn Nycklar framgångsrikt importerade @@ -1253,7 +1221,6 @@ Skapa Hem Bjöd in - Du har blivit utsparkad från %1$s av %2$s Du har blivit bannad från %1$s av %2$s Orsak: %1$s @@ -1319,10 +1286,8 @@ Säkerhetskopierar %d nycklar… Signatur - Verifierad! Jag förstår - Verifieringsbegäran Okänt fel Det verkar som att du försöker ansluta till en annan hemserver. Vill du logga ut\? @@ -1525,7 +1490,6 @@ Verifiera %s Verifierade %s Väntar på %s… - Säkerhet Adminhandlingar Lämnar rummet… @@ -1830,7 +1794,6 @@ Dölj avancerat Visa avancerat %1$d av %2$d - Ge samtycke Återkalla mitt samtycke Du har gett samtycke att skicka e-postadresser och telefonnummer till den här identitetsservern för att upptäcka andra användare baserat på dina kontakter. @@ -1914,8 +1877,6 @@ Flytta Anslut Rådfråga först - - Aktivt samtal (%1$s) Ett fel inträffade när telefonnumret slogs upp Knappsats @@ -2003,7 +1964,7 @@ Jag och mina teamkamrater Att privat utrymme för att organisera dina rum Bara jag - Det till att rätt personer har åtkomst till %s. Du kan ändra detta senare. + Det till att rätt personer har åtkomst till %s. Vem jobbar du med\? För att gå med i ett existerande utrymme så behöver du en inbjudan. Detta kan ändras senare @@ -2112,7 +2073,6 @@ Ange namnet för en ny server du vill utforska. Lägg till en ny server Din server - För att utföra detta, vänligen ge kameraåtkomst från systeminställningarna. Vissa behörigheter saknas för att utföra detta, vänligen ge behörighet från systeminställningarna. Observera att uppgradering kommer att göra en ny version av rummet. Alla nuvarande meddelanden kommer att vara kvar i det här arkiverade rummet. @@ -2313,7 +2273,6 @@ Omröstningens fråga eller ämne Skapa omröstning Omröstning - Skicka e-postadresser och telefonnummer till %s Dina kontakter är privata. För att upptäcka användare från dina kontakter så behöver vi ditt tillstånd att skicka kontaktinfo till din identitetsserver. Sessionen har loggats ut! @@ -2446,4 +2405,18 @@ Kopiera länk till tråd Visa i rum Visa trådar + Rumsaviseringar + Användare + Avisera hela rummet + + %1$d till + %1$d till + + Visa mindre + %1$s, %2$s och fler + %1$s och %2$s + + %d server-ACL-ändring + %d server-ACL-ändringar + \ No newline at end of file From a4f04b704fbae4796c1592c678a9b3f19bae88c8 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 25 Feb 2022 22:42:34 +0000 Subject: [PATCH 0024/1430] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2157 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 35 ++--------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 066a40fdf4..2f34fc66fb 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -39,7 +39,6 @@ Помилка Matrix Адреса електронної пошти Номер телефону - %s оновлює цю кімнату. Початкове налаштування: \nІмпортування даних облікового запису @@ -164,7 +163,6 @@ Видалити Перейменувати Поскаржитись на вміст - або Запрошення Вийти з облікового запису @@ -187,7 +185,6 @@ Лише Matrix-контакти Немає результатів Кімнати - Надіслати журнали Надіслати журнали помилок Надіслати знімок екрана @@ -235,7 +232,6 @@ Інформація Для здійснення аудіодзвінків потрібен доступ до мікрофону. Для здійснення відеодзвінків потрібен доступ до камери та мікрофону.\n\nБудь ласка, надайте його у наступних виринаючих вікнах, щоб мати змогу їх здійснити. - ТАК НІ Продовжити @@ -269,7 +265,6 @@ Сертифікат почав відрізнятися від того, якому довіряв ваш телефон. Це ДУЖЕ НЕЗВИЧНО. Наполегливо радимо НЕ ПРИЙМАТИ цей новий сертифікат. Сертифікат змінився з довіреного на недовірений. Сервер міг оновити свій сертифікат. Зв\'яжіться з адміністратором сервера, щоб отримати дійсний відбиток. Приймайте сертифікат лише у випадку збігу відбитку вище з відбитком, оприлюдненим адміністратором сервера. - Пошук Фільтр переліку користувачів Тут порожньо @@ -370,7 +365,6 @@ Експорт Введіть парольну фразу Підтвердіть парольну фразу - Імпортувати E2E ключі кімнати Імпортувати ключі кімнати Імпортувати ключі з локального файлу @@ -382,7 +376,6 @@ Звірити Підтвердьте, порівнявши вказане за допомогою налаштувань користувача в іншому сеансі: Якщо вони відрізняються, безпека вашого зв\'язку може бути під загрозою. - Вибір каталогу кімнат Ім\'я сервера Всі кімнати на сервері %s @@ -439,7 +432,6 @@ У вас поки що не має наліпок. \n \nДодати зараз\? - %d учасник %d учасники @@ -466,18 +458,12 @@ Помилка Системні сповіщення Якщо можливо, будь ласка, напишіть опис англійською. - - - - %d вибрано %d вибрано %d вибрано %d вибрано - - Попередній перегляд посилань Попередній перегляд медіа перед надсиланням ${app_name} збирає анонімну аналітику, щоб ми могли вдосконалювати цей додаток. @@ -490,7 +476,6 @@ %d непрочитаних сповіщень - %d кімната %d кімнати @@ -522,8 +507,6 @@ Домівка Кімнати Запрошено - - %2$s вилучає вас із %1$s %2$s блокує вас у %1$s Причина: %1$s @@ -753,7 +736,6 @@ Не вдалося встановити зв’язок у режимі реального часу. \nПопросіть адміністратора вашого домашнього сервера налаштувати сервер TURN для надійної роботи викликів. ${app_name} не вдалося здійснити виклик - Більше немає результатів Відкликати публікування Додати @@ -784,7 +766,6 @@ Схоже у вас вже є резервна копія ключа налаштування з іншого сеансу. Хочете замінити його тим, який ви створюєте\? Резервна копія вже існує на вашому homeserver Ключ відновлення збережено. - Зберегти як файл Поділитися Зберегти ключ відновлення @@ -932,7 +913,6 @@ Інтеграцію вимкнено Керування інтеграцією Дозволити інтеграції - Це замінить ваш поточний ключ або фразу. Створіть новий ключ безпеки або встановіть нову фразу безпеки для наявної резервної копії. Захистіться від втрати доступу до зашифрованих повідомлень і даних створенням резервної копії ключів шифрування на своєму сервері. @@ -950,7 +930,6 @@ %d секунд %d секунд - Ви не отримуватимете сповіщення про вхідні повідомлення, коли програма перебуває у фоновому режимі. Немає фонової синхронізації ${app_name} періодично синхронізуватиметься у фоновому режимі в певний час (налаштовується). @@ -1273,9 +1252,7 @@ Ви утримали виклик %s утримали виклик Утримати - Активний виклик (%1$s) - Змінити мережу Змінити Push-сповіщення вимкнено @@ -1372,7 +1349,6 @@ Зазначте адресу сервера ідентифікації Неможливо під\'єднатись до сервера ідентифікації Зазначте адресу сервера ідентифікації - Повторити Від\'єднання від вашого сервера ідентифікації означатиме, що ви не будете виявними для інших користувачів та не зможете запрошувати інших через електронну пошту або номер телефону. Ви наразі не використовуєте жодного сервера ідентифікації. Для того, щоб виявляти інших та бути виявним для знайомих вам наявних контактів, налаштуйте такий сервер нижче. @@ -1786,8 +1762,6 @@ %s хоче звірити ваш сеанс Запит перевірки Запит перевірки - - Зрозуміло Резервні копії всіх ключів створено Резервне копіювання ключів. Це може тривати кілька хвилин… @@ -1823,14 +1797,12 @@ Перегляд реакцій Тут буде показано ваші кімнати. Натисніть + унизу праворуч, щоб знайти наявні або створити власні. Схоже, ви намагаєтесь під\'єднатися до іншого домашнього сервера. Бажаєте вийти\? - Резервне копіювання %d ключа… Резервне копіювання %d ключів… Резервне копіювання %d ключів… Резервне копіювання %d ключів… - Додати наявну кімнату до простору Створити простір Лише я @@ -1844,7 +1816,6 @@ Сталася помилка пошуку номера телефона Ви відхилили цей виклик Ваша книга контактів порожня - Закрити нагадування про резервне копіювання ключів Схоже, що відповідь сервера надто тривала, це може бути спричинено або поганим з’єднанням, або помилкою сервера. Повторіть спробу через деякий час. Повторіть спробу, коли погодитесь з умовами свого домашнього сервера. @@ -2285,7 +2256,6 @@ \n \nБажаєте зайти через вебклієнт\? Щоб знайти наявні контакти, надішліть дані контактів (е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності. - Ваші контакти приватні. Щоб дізнаватись про користувачів, відповідних вашим контактам, дозвольте нам надсилати дані ваших контактів серверу ідентифікації. Надіслати електронні адреси та номери телефонів %s Сеанс завершено! @@ -2337,7 +2307,6 @@ Кімната — версії %s, яку домашній сервер позначив нестабільною. Поліпшення кімнати — серйозна операція. Її зазвичай радять, коли кімната нестабільна через вади, брак функціоналу чи вразливості безпеки. \nЗазвичай це впливає лише на деталі опрацювання кімнати сервером. - Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення. Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення. \nУ вас нема дозволу додавати кімнати. @@ -2382,14 +2351,12 @@ Якщо скасуєте це й загубите пристрій, то втратите зашифровані повідомлення й дані. \n \nВвімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях. - Скасування залишить %1$s (%2$s) без звірки. У їхньому користувацькому профілі можна почати заново. Звірте цим сеансом свій новий. Це надасть йому доступ до зашифрованих повідомлень. Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс. Якщо ви увімкнете шифрування для кімнати, його неможливо буде вимкнути. Надіслані у зашифровану кімнату повідомлення будуть прочитними тільки для учасників кімнати, натомість для сервера вони будуть непрочитними. Увімкнення шифрування може унеможливити роботу ботів та мостів. Не вдалося поширити звірку цього сеансу з вашими іншими. \nЗвірка збережеться локально, її поширить майбутня версія застосунку. - Можете ввімкнути це, якщо в кімнаті співпрацюватимуть лише внутрішні команди на вашому домашньому сервері. Цього більше не можна буде змінити. Цей сеанс — користувача %1$s, а ви надаєте облікові дані користувача %2$s. Це не підтримується в ${app_name}. \nБудь ласка, спершу очистіть дані, а тоді ввійдіть в інший обліковий запис. @@ -2542,4 +2509,6 @@ І ще %1$d Згорнути + %1$s, %2$s та інші + %1$s і %2$s \ No newline at end of file From 1f6275762ecf8507a7413acbf9f37cddb7d1c1f0 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 25 Feb 2022 20:29:56 +0000 Subject: [PATCH 0025/1430] Translated using Weblate (Swedish) Currently translated at 100.0% (51 of 51 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40104000.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40104020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40104000.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40104020.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104000.txt b/fastlane/metadata/android/sv-SE/changelogs/40104000.txt new file mode 100644 index 0000000000..6bce52ba36 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Initial implementation av trådmeddelanden. Meddelandebubblor. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104020.txt b/fastlane/metadata/android/sv-SE/changelogs/40104020.txt new file mode 100644 index 0000000000..e3b5d4cd1c --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: lägg till stöd för @room och slutna omröstningar, och många andra små ändringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.2 From 329ce7736ca0d3572ff9f0a64929e5cf57511236 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 26 Feb 2022 08:39:52 +0000 Subject: [PATCH 0026/1430] Translated using Weblate (Japanese) Currently translated at 54.9% (28 of 51 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/ --- fastlane/metadata/android/ja-JP/changelogs/40101040.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101100.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103070.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103080.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103100.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103110.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103120.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104000.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104020.txt | 2 ++ 9 files changed, 18 insertions(+) create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101040.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101100.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103070.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103080.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103100.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103110.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103120.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104000.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104020.txt diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101040.txt b/fastlane/metadata/android/ja-JP/changelogs/40101040.txt new file mode 100644 index 0000000000..2dc1cdb781 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101040.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:パフォーマンスの向上と不具合の修正 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.4 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101100.txt b/fastlane/metadata/android/ja-JP/changelogs/40101100.txt new file mode 100644 index 0000000000..2f720498ec --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101100.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:テーマ、スタイルの更新と、スペースに関する新機能。 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.10 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103070.txt b/fastlane/metadata/android/ja-JP/changelogs/40103070.txt new file mode 100644 index 0000000000..09c44e990d --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103070.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:主に通知に関する不具合の修正。 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103080.txt b/fastlane/metadata/android/ja-JP/changelogs/40103080.txt new file mode 100644 index 0000000000..7c37f5a756 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103080.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:不具合の修正 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.8 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103100.txt b/fastlane/metadata/android/ja-JP/changelogs/40103100.txt new file mode 100644 index 0000000000..76c28cdd90 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103100.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:投票機能のサポート(実験的)。URL プレビューの新規デザイン。 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.10 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103110.txt b/fastlane/metadata/android/ja-JP/changelogs/40103110.txt new file mode 100644 index 0000000000..5295af5833 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103110.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:不具合の修正 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.11 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103120.txt b/fastlane/metadata/android/ja-JP/changelogs/40103120.txt new file mode 100644 index 0000000000..3859bee8d5 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103120.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:不具合の修正 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.12 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104000.txt b/fastlane/metadata/android/ja-JP/changelogs/40104000.txt new file mode 100644 index 0000000000..22a205dc37 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104000.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:スレッド機能の実装、吹き出しメッセージ。 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104020.txt b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt new file mode 100644 index 0000000000..e792008faf --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点:@roomの対応、非公開の投票など。 +更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.4.2 From eda723c23082382b9f013493b8223195036a2d7d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Feb 2022 12:35:27 +0200 Subject: [PATCH 0027/1430] Remove fetching thread summaries when homeserver do not support MSC3440 --- .../session/homeserver/GetCapabilitiesResult.kt | 3 ++- .../room/threads/list/viewmodel/ThreadListViewModel.kt | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 55526b41db..3a016bc3e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -70,7 +70,8 @@ internal data class Capabilities( * Capability to indicate if the server supports MSC3440 Threading * True if the user can use m.thread relation, false otherwise */ - @Json(name = "m.thread") +// @Json(name = "m.thread") + @Json(name = "io.element.thread") val threads: BooleanCapability? = null ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 290b71a504..d68e0a3248 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -54,8 +54,7 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState } init { - observeThreads() - fetchThreadList() + fetchAndObserveThreads() } override fun handle(action: EmptyAction) {} @@ -64,9 +63,12 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState * Observing thread list with respect to homeserver * capabilities */ - private fun observeThreads() { + private fun fetchAndObserveThreads() { when (session.getHomeServerCapabilities().canUseThreading) { - true -> observeThreadSummaries() + true -> { + fetchThreadList() + observeThreadSummaries() + } false -> observeThreadsList() } } From e59f2bba0a05b6962c8ebc40261af993331a2283 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Feb 2022 17:13:06 +0200 Subject: [PATCH 0028/1430] Add analytics to threads --- .../session/room/timeline/TimelineEvent.kt | 8 ++ .../analytics/extensions/ComposerExt.kt | 28 ++++++ .../analytics/extensions/InteractionExt.kt | 24 +++++ .../app/features/analytics/plan/Composer.kt | 5 + .../features/analytics/plan/Interaction.kt | 95 +++++++++++++++++-- .../features/analytics/plan/MobileScreen.kt | 5 + .../home/room/detail/TimelineFragment.kt | 13 +-- .../composer/MessageComposerViewModel.kt | 4 + .../composer/MessageComposerViewState.kt | 3 + .../timeline/action/EventSharedAction.kt | 2 +- .../action/MessageActionsViewModel.kt | 3 +- .../home/room/threads/ThreadsActivity.kt | 3 + .../threads/arguments/ThreadTimelineArgs.kt | 3 +- .../list/viewmodel/ThreadListViewModel.kt | 7 +- .../threads/list/views/ThreadListFragment.kt | 2 + 15 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt create mode 100644 vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index c03d0fd17b..d7796c8808 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room.timeline import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType @@ -159,6 +160,13 @@ fun TimelineEvent.isSticker(): Boolean { return root.isSticker() } +/** + * Returns whether or not the event is a root thread event + */ +fun TimelineEvent.isRootThread(): Boolean { + return root.threadDetails?.isRootThread.orFalse() +} + /** * Get the latest message body, after a possible edition, stripping the reply prefix if necessary */ diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt new file mode 100644 index 0000000000..80675ac57c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 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.app.features.analytics.extensions + +import im.vector.app.features.analytics.plan.Composer +import im.vector.app.features.home.room.detail.composer.MessageComposerViewState +import im.vector.app.features.home.room.detail.composer.SendMode + +fun MessageComposerViewState.toAnalyticsComposer(): Composer = + Composer( + inThread = isInThreadTimeline(), + isEditing = sendMode is SendMode.Edit, + isReply = sendMode is SendMode.Reply, + startsThread = startsThread) diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt new file mode 100644 index 0000000000..c46230cdd1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 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.app.features.analytics.extensions + +import im.vector.app.features.analytics.plan.Interaction + +fun Interaction.Name.toAnalyticsInteraction(interactionType: Interaction.InteractionType = Interaction.InteractionType.Touch) = + Interaction( + name = this, + interactionType = interactionType) diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt index a3b847a1bd..79be8aae2b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt @@ -39,6 +39,10 @@ data class Composer( * sent event. */ val isReply: Boolean, + /** + * Whether this message begins a new thread or not. + */ + val startsThread: Boolean? = null, ) : VectorAnalyticsEvent { override fun getName() = "Composer" @@ -48,6 +52,7 @@ data class Composer( put("inThread", inThread) put("isEditing", isEditing) put("isReply", isReply) + startsThread?.let { put("startsThread", it) } }.takeIf { it.isNotEmpty() } } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt index 7bdc7740e1..2007f75fbc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt @@ -40,6 +40,36 @@ data class Interaction( ) : VectorAnalyticsEvent { enum class Name { + /** + * User tapped on Add to Home button on Room Details screen. + */ + MobileRoomAddHome, + + /** + * User tapped on Leave Room button on Room Details screen. + */ + MobileRoomLeave, + + /** + * User tapped on Threads button on Room screen. + */ + MobileRoomThreadListButton, + + /** + * User tapped on a thread summary item on Room screen. + */ + MobileRoomThreadSummaryItem, + + /** + * User tapped on the filter button on ThreadList screen. + */ + MobileThreadListFilterItem, + + /** + * User selected a thread on ThreadList screen. + */ + MobileThreadListThreadItem, + /** * User tapped the already selected space from the space list. */ @@ -52,8 +82,8 @@ data class Interaction( SpacePanelSwitchSpace, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the create room button in the add existing room to space + * dialog in Element Web/Desktop. */ WebAddExistingToSpaceDialogCreateRoomButton, @@ -105,12 +135,24 @@ data class Interaction( */ WebRightPanelRoomUserInfoInviteButton, + /** + * User clicked the threads 'show' filter dropdown in the threads panel + * in Element Web/Desktop. + */ + WebRightPanelThreadPanelFilterDropdown, + /** * User clicked the create room button in the room directory of Element * Web/Desktop. */ WebRoomDirectoryCreateRoomButton, + /** + * User clicked the Threads button in the top right of a room in Element + * Web/Desktop. + */ + WebRoomHeaderButtonsThreadsButton, + /** * User adjusted their favourites using the context menu on the header * of a room in Element Web/Desktop. @@ -153,6 +195,12 @@ data class Interaction( */ WebRoomListHeaderPlusMenuCreateRoomItem, + /** + * User clicked the explore rooms button in the + context menu of the + * room list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuExploreRoomsItem, + /** * User adjusted their favourites using the context menu on a room tile * in the room list in Element Web/Desktop. @@ -189,6 +237,12 @@ data class Interaction( */ WebRoomListRoomsSublistPlusMenuCreateRoomItem, + /** + * User clicked the explore rooms button in the + context menu of the + * rooms sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuExploreRoomsItem, + /** * User interacted with leave action in the general tab of the room * settings dialog in Element Web/Desktop. @@ -201,6 +255,12 @@ data class Interaction( */ WebRoomSettingsSecurityTabCreateNewRoomButton, + /** + * User clicked a thread summary in the timeline of a room in Element + * Web/Desktop. + */ + WebRoomTimelineThreadSummaryButton, + /** * User interacted with the theme radio selector in the Appearance tab * of Settings in Element Web/Desktop. @@ -214,17 +274,40 @@ data class Interaction( WebSettingsSidebarTabSpacesCheckbox, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the explore rooms button in the context menu of a space + * in Element Web/Desktop. + */ + WebSpaceContextMenuExploreRoomsItem, + + /** + * User clicked the home button in the context menu of a space in + * Element Web/Desktop. + */ + WebSpaceContextMenuHomeItem, + + /** + * User clicked the new room button in the context menu of a space in + * Element Web/Desktop. */ WebSpaceContextMenuNewRoomItem, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the new room button in the context menu on the space + * home in Element Web/Desktop. */ WebSpaceHomeCreateRoomButton, + /** + * User clicked the back button on a Thread view going back to the + * Threads Panel of Element Web/Desktop. + */ + WebThreadViewBackButton, + + /** + * User selected a thread in the Threads panel in Element Web/Desktop. + */ + WebThreadsPanelThreadItem, + /** * User clicked the theme toggle button in the user menu of Element * Web/Desktop. diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt index 758a0540bf..33976cb4cc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt @@ -225,6 +225,11 @@ data class MobileScreen( */ SwitchDirectory, + /** + * Screen that displays list of threads for a room + */ + ThreadList, + /** * A screen that shows information about a room member. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 1a40018526..67fb595378 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -119,7 +119,8 @@ import im.vector.app.core.utils.startInstallFromSourceIntent import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogReportContentBinding import im.vector.app.databinding.FragmentTimelineBinding -import im.vector.app.features.analytics.plan.Composer +import im.vector.app.features.analytics.extensions.toAnalyticsInteraction +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentsHelper @@ -1505,9 +1506,6 @@ class TimelineFragment @Inject constructor( return } if (text.isNotBlank()) { - withState(messageComposerViewModel) { state -> - analyticsTracker.capture(Composer(isThreadTimeLine(), isEditing = state.sendMode is SendMode.Edit, isReply = state.sendMode is SendMode.Reply)) - } // We collapse ASAP, if not there will be a slight annoying delay views.composerLayout.collapse(true) lockSendButton = true @@ -2204,7 +2202,7 @@ class TimelineFragment @Inject constructor( } is EventSharedAction.ReplyInThread -> { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { - navigateToThreadTimeline(action.eventId) + navigateToThreadTimeline(action.eventId, action.startsThread) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } @@ -2363,9 +2361,11 @@ class TimelineFragment @Inject constructor( * using the ThreadsActivity */ - private fun navigateToThreadTimeline(rootThreadEventId: String) { + private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false) { + analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction()) context?.let { val roomThreadDetailArgs = ThreadTimelineArgs( + startsThread = startsThread, roomId = timelineArgs.roomId, displayName = timelineViewModel.getRoomSummary()?.displayName, avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl, @@ -2381,6 +2381,7 @@ class TimelineFragment @Inject constructor( */ private fun navigateToThreadList() { + analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction()) context?.let { val roomThreadDetailArgs = ThreadTimelineArgs( roomId = timelineArgs.roomId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 6adf248af9..a07d01fed5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -27,6 +27,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.command.CommandParser @@ -188,6 +189,9 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleSendMessage(action: MessageComposerAction.SendMessage) { withState { state -> + analyticsTracker.capture(state.toAnalyticsComposer()).also { + setState { copy(startsThread = false) } + } when (state.sendMode) { is SendMode.Regular -> { when (val slashCommandResult = commandParser.parseSlashCommand( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index f90f3975c6..95553eb1cd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer import com.airbnb.mvrx.MavericksState import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** @@ -62,6 +63,7 @@ data class MessageComposerViewState( val canSendMessage: CanSendStatus = CanSendStatus.Allowed, val isSendButtonVisible: Boolean = false, val rootThreadEventId: String? = null, + val startsThread: Boolean = false, val sendMode: SendMode = SendMode.Regular("", false), val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle ) : MavericksState { @@ -80,6 +82,7 @@ data class MessageComposerViewState( constructor(args: TimelineArgs) : this( roomId = args.roomId, + startsThread = args.threadTimelineArgs?.startsThread.orFalse(), rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId) fun isInThreadTimeline(): Boolean = rootThreadEventId != null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index 048a4754f5..5f12c2f174 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -48,7 +48,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Reply(val eventId: String) : EventSharedAction(R.string.reply, R.drawable.ic_reply) - data class ReplyInThread(val eventId: String) : + data class ReplyInThread(val eventId: String, val startsThread: Boolean) : EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread) object ViewInRoom : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 745cb0c731..20cf0b46fd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -61,6 +61,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.api.session.room.timeline.isPoll +import org.matrix.android.sdk.api.session.room.timeline.isRootThread import org.matrix.android.sdk.api.session.room.timeline.isSticker import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -328,7 +329,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { - add(EventSharedAction.ReplyInThread(eventId)) + add(EventSharedAction.ReplyInThread(eventId, !timelineEvent.isRootThread())) } if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index fc76535c4c..726138ed93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -26,6 +26,8 @@ import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityThreadsBinding +import im.vector.app.features.analytics.extensions.toAnalyticsInteraction +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.detail.arguments.TimelineArgs @@ -92,6 +94,7 @@ class ThreadsActivity : VectorBaseActivity() { * One usage of that is from the Threads Activity */ fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) { + analyticsTracker.capture(Interaction.Name.MobileThreadListThreadItem.toAnalyticsInteraction()) val commonOption: (FragmentTransaction) -> Unit = { it.setCustomAnimations( R.anim.animation_slide_in_right, diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt index aadad3d97c..d3a80811ea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt @@ -26,5 +26,6 @@ data class ThreadTimelineArgs( val displayName: String?, val avatarUrl: String?, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, - val rootThreadEventId: String? = null + val rootThreadEventId: String? = null, + val startsThread: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index d68e0a3248..8da9d83e10 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -25,6 +25,9 @@ import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toAnalyticsInteraction +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -34,6 +37,7 @@ import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent import org.matrix.android.sdk.flow.flow class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState, + private val analyticsTracker: AnalyticsTracker, private val session: Session) : VectorViewModel(initialState) { @@ -113,9 +117,10 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState } } - fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading + fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading fun applyFiltering(shouldFilterThreads: Boolean) { + analyticsTracker.capture(Interaction.Name.MobileThreadListFilterItem.toAnalyticsInteraction()) setState { copy(shouldFilterThreads = shouldFilterThreads) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index 949778629b..d5659efa49 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentThreadListBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator import im.vector.app.features.home.room.threads.ThreadsActivity @@ -62,6 +63,7 @@ class ThreadListFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.ThreadList } override fun onOptionsItemSelected(item: MenuItem): Boolean { From f0f98ce019e87eb47532fdb869c9612298de0958 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Feb 2022 17:44:53 +0200 Subject: [PATCH 0029/1430] Add changelog file --- changelog.d/5378.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5378.misc diff --git a/changelog.d/5378.misc b/changelog.d/5378.misc new file mode 100644 index 0000000000..1cf6da5e59 --- /dev/null +++ b/changelog.d/5378.misc @@ -0,0 +1 @@ +Add analytics support for threads \ No newline at end of file From 12ea262ebc3d9e960a0dd8ee97468bedd3629c0b Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Mon, 28 Feb 2022 20:45:22 +0000 Subject: [PATCH 0030/1430] Translated using Weblate (Japanese) Currently translated at 98.0% (2116 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 0d35cc8c99..2c17a90df8 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -2322,4 +2322,5 @@ \n検証は端末に保存され、新しいバージョンのアプリで共有されます。 %1$s、%2$s他 %1$sと%2$s + 自分と相手を認証してチャットを安全に保ちます \ No newline at end of file From 2673f6715a317fef69cc115119e288a3ce7530db Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 28 Feb 2022 20:44:45 +0000 Subject: [PATCH 0031/1430] Translated using Weblate (Japanese) Currently translated at 98.0% (2116 of 2157 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 68 +++++++++++++---------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 2c17a90df8..fe125881f4 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -79,7 +79,7 @@ クリップボードへコピー 警告 お気に入り - 知人 + メンバー ルーム ルーム名で絞り込む 招待中 @@ -713,7 +713,7 @@ これを行うには設定から「インテグレーションを許可」を有効にしてください。 インテグレーションが無効になっています インテグレーションマネージャー - インテグレーション(統合)を許可 + インテグレーションを許可 FCMトークンが正常に取得されました: \n%1$s Firebaseトークン @@ -957,8 +957,8 @@ /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信 チャットでエフェクトを表示 ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。 - ボット、ブリッジ、ウィジェット、ステッカーパックの管理をします。 -\nインテグレーションマネージャーは、構成データを受信し、ユーザーに代わってウィジェットの変更、ルーム招待の送信、権限の設定などを行うことができます。 + ボット、ブリッジ、ウィジェット、ステッカーパックを管理します。 +\nインテグレーションマネージャーは、構成データを受信し、ユーザーに代わってウィジェットの変更や、ルーム招待の送信、権限の設定などを行うことができます。 インテグレーション(統合) アプリがバックグラウンドにある場合、着信メッセージは通知されません。 ${app_name}は正確な時間に定期的にバックグラウンドで同期します(構成可能)。 @@ -1296,7 +1296,7 @@ プッシュ通知に関するルール あなたは既にこのルームを見ています! その他のサードパーティーの使用に関する掲示 - Matrix SDKバージョン + Matrix SDKのバージョン ファイル\"%1$s\"からエンドツーエンド暗号鍵をインポートします。 鍵のバックアップデータの取得中にエラーが発生しました 信頼情報の取得中にエラーが発生しました @@ -1314,7 +1314,7 @@ お待ち下さい… ネットワークがありません。インターネット接続を確認してください。 不正な形式のイベントです。表示できません - ルーム管理者によってモデレートされたイベント + ルームの管理者によってモデレートされたイベント リアクション リアクションを見る リアクションを追加 @@ -1463,7 +1463,7 @@ コンテンツが報告されました ヘルプとサポート ヘルプ - ${app_name}のポリシー + ${app_name}の運営方針 ここ キーワードを追加 自分のスレッド @@ -1564,7 +1564,7 @@ 応答がありません スレッドへのリンクをコピー 有効にする - あなたのIDサーバーのポリシー + あなたのIDサーバーの運営方針 新しいルームを作成 認証コードが正しくありません。 IDサーバーのURLを入力してください @@ -1602,7 +1602,7 @@ 添付ファイルの取得中にエラーが発生しました。 鍵のバックアップのバナーを閉じる キーワードに「%s」を含めることはできません - %s へのメール通知を有効にする + %sへのメール通知を有効にする ヒント:メッセージを長押しして「%s」を選択。 スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。 あなたの非公開スペース @@ -1646,7 +1646,7 @@ 音声メッセージ(%1$s) 推奨のルームバージョンへとアップグレード 音声メッセージを録音 - あなたのホームサーバーのポリシー + あなたのホームサーバーの運営方針 一番下に移動 %sが読みました %1$sと%2$sが読みました @@ -1698,7 +1698,7 @@ %d人のユーザーが読みました スペースへのアクセス - このサーバーはポリシーを提供していません。 + このサーバーは運営方針を提供していません。 数秒かかるかもしれません。少々お待ちください。 利用可能な言語を読み込んでいます… ユーザーを招待 @@ -1706,14 +1706,14 @@ パスワードを選択してください。 メンバーを追加 ログインを検証 - メッセージ… + メッセージを送る… このファイルは大きすぎてアップロードできません。 この情報の送信に同意しますか? 連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 メールアドレスと電話番号を%sに送信 - このIDサーバーはポリシーを提供していません - IDサーバーのポリシーを隠す - IDサーバーのポリシーを表示 + このIDサーバーは運営方針を提供していません + IDサーバーの運営方針を隠す + IDサーバーの運営方針を表示 アカウントの新しいパスワードを設定… シェイクを検出しました! 電話を振って、しきい値を試してください @@ -1888,7 +1888,7 @@ スペースは、ルームや連絡先をグループ化する新しい方法です。 招待されています 新しいスペースを、あなたが管理するスペースに追加。 - 注意:アプリケーションは再起動します + 注意:アプリケーションが再起動します ホームサーバーの管理者にお問い合わせください あなたが参加している全てのルームがホームに表示されます。 親のスペースを自動的に更新 @@ -1932,7 +1932,7 @@ メッセージを送信できませんでした ウィジェットを開く %1$sに転送 - 通話は終了しました + 通話が終了しました 自分自身にダイレクトメッセージを送信することはできません! 電話番号(任意) 電話番号を確認 @@ -2038,7 +2038,7 @@ 位置情報を共有しました %sでリアクションしました 検証終了 - 次のいずれかのセキュリティが破られている可能性があります。 + 次のいずれかのセキュリティーが破られている可能性があります。 \n \n - あなたのホームサーバー \n - 検証している相手のホームサーバー @@ -2091,7 +2091,7 @@ ユーザーを招待できませんでした。招待したいユーザーを確認して、もう一度試してください。 %sの利用規約を開く ユーザーによる同意は与えられていません。 - 代わりに、他のIDサーバーのURLを入力できます + または、他のIDサーバーのURLを入力できます サーバー上の暗号鍵をバックアップして、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう。 いまキャンセルすると、ログインできなくなった際に、暗号化されたメッセージとデータを失ってしまう可能性があります。 \n @@ -2200,7 +2200,7 @@ %sを待機しています… このユーザーがこのセッションを検証するまで、送受信されるメッセージには警告マークが付きます。手動で検証することも可能です。 セッションの取得に失敗しました - チームメイトは誰ですか? + 誰がチームの仲間ですか? %sを探索できるようになります 私のスペース %1$s %2$s に参加してください スキップ @@ -2228,21 +2228,21 @@ 再認証が必要です 全てリセット 連絡先 - 検証がキャンセルされました。再び検証を開始することができます。 + 検証をキャンセルしました。あらためて開始してください。 押し続けて録音し、離すと送信 - セキュリティー向上のため、PINコードを選択してください + PINコードを設定してください %d個のサーバーアクセス制御リストの変更 置き換えられたルームに参加 このルームが発見できません。存在することを確認してください。 - 指紋や顔画像など、端末に固有の生体認証を有効にしてください。 + 指紋や顔画像など、端末に固有の生体認証を有効にする。 絵文字で検証 テキストで検証 すべてのセッションを検証し、アカウントとメッセージが安全であることを確認してください ログインしている場所を確認 復旧用の手段を全て無くしてしまいましたか?全てリセットする - 、あるいはクロス署名に対応した他のMatrixのクライアント + クロス署名に対応した他のMatrixのクライアントでも使用できます。 どのような議論を%sで行いたいですか? クロス署名の設定に失敗しました 履歴とメッセージが消去され、信頼済の端末、信頼済のユーザーが取り消されます @@ -2252,7 +2252,7 @@ 電話番号を検索する際にエラーが発生しました 着信を拒否しました それぞれにルームを作りましょう。後から追加することもできます(既にあるルームも追加できます)。 - 分かるように特徴を記入してください。これはいつでも変更できます。 + このスペースを特定できるような特徴を記入してください。これはいつでも変更できます。 目立つように特徴を記入してください。これはいつでも変更できます。 未読のメッセージ数のみを通知に表示。 2分間${app_name}を使用しないと、PINコードが要求されます。 @@ -2297,8 +2297,8 @@ エラーのためメッセージが送信されませんでした %sにいない人を探していますか? 直接${app_name}で招待を受け取るには、設定画面から%sしてください。 - PINコードを入力しなければ${app_name}のロックを解除することはできません。 - ${app_name}を開く際にはPINコードの入力が必要です。 + PINコードでしか${app_name}のロックを解除することはできません。 + ${app_name}を開く際には、毎回PINコードの入力が必要です。 あなたがブロックされているルームを開くことはできません。 PINコードの検証に失敗しました。新しいコードを入力してください。 端末の連絡先がありません @@ -2308,7 +2308,7 @@ このメッセージを待機しています。時間がかかる可能性があります ルームの設定の変更に成功しました 確認のため、セキュリティーフレーズを再入力してください。 - ホームサーバー(%1$s)が、IDサーバーに%2$sを設定するよう提案しています + ホームサーバー(%1$s)は、IDサーバーに%2$sを設定するように提案しています IDサーバー %s から切断しますか? ダイレクトメッセージを作成できませんでした。招待したユーザーを確認し、もう一度やり直してください。 セキュリティーフレーズ @@ -2323,4 +2323,16 @@ %1$s、%2$s他 %1$sと%2$s 自分と相手を認証してチャットを安全に保ちます + あなたしか知らないセキュリティーフレーズを入力してください。サーバーで機密情報を保護するために使用します。 + 監査結果をエクスポート + ストレージから機密情報を発見できません + 操作を実行できません。ホームサーバーは最新のバージョンではありません。 + ビデオ通話が拒否されました + 音声通話が拒否されました + %1$sは通話を拒否しました + このデバイスを認証可能な他の端末が全くない場合にのみ、続行してください。 + このセッションを信頼済として検証すると、暗号化されたメッセージにアクセスすることができます。このアカウントにサインインしなかった場合は、あなたのアカウントのセキュリティーが破られている可能性があります: + アカウントのセキュリティーが破られている可能性があります + 選択したスペースに追加 + 最新の${app_name}は他のデバイスでも使用できます: \ No newline at end of file From cb00a668fe48b0d5f0ea77ecaaf1a9848719341a Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Tue, 1 Mar 2022 11:42:54 +0000 Subject: [PATCH 0032/1430] Format unit test results as well --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d6e194916b..3d24108084 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,6 +31,9 @@ jobs: ${{ runner.os }}-gradle- - name: Run unit tests run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false --stacktrace + - name: Format unit test results + if: always() + run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 if: always() && From 7837d1d6d65e4287152cc7f7105253ba7575369d Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:05:07 +0000 Subject: [PATCH 0033/1430] Wrap up the argument list; it can be long --- tools/ci/render_test_output.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/ci/render_test_output.py b/tools/ci/render_test_output.py index 48dd3987a3..9df3170058 100755 --- a/tools/ci/render_test_output.py +++ b/tools/ci/render_test_output.py @@ -9,9 +9,9 @@ import sys import xml.etree.ElementTree as ET suitename = sys.argv[1] xmlfiles = sys.argv[2:] - -print(f"Arguments: {sys.argv}") - +print("::group::Arguments") +print(f"{sys.argv}") +print("::endgroup::") for xmlfile in xmlfiles: print(f"Handling: {xmlfile}") tree = ET.parse(xmlfile) From 214e0efcd990a6b4c9d491819e010dff9cc13069 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 2 Mar 2022 13:47:08 +0200 Subject: [PATCH 0034/1430] Add Markdown support to thread summaries and thread list --- .../sdk/api/session/threads/ThreadDetails.kt | 3 +- .../internal/database/mapper/EventMapper.kt | 2 +- .../detail/search/SearchResultController.kt | 3 + .../room/detail/search/SearchResultItem.kt | 4 +- .../format/DisplayableEventFormatter.kt | 100 +++++++++++++++++- .../helper/MessageItemAttributesFactory.kt | 3 + .../detail/timeline/item/AbsMessageItem.kt | 3 +- .../list/viewmodel/ThreadListController.kt | 38 +++++-- 8 files changed, 139 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt index fafe17b2c0..d6937d5b26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.api.session.threads +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.sender.SenderInfo /** @@ -26,7 +27,7 @@ data class ThreadDetails( val isRootThread: Boolean = false, val numberOfThreads: Int = 0, val threadSummarySenderInfo: SenderInfo? = null, - val threadSummaryLatestTextMessage: String? = null, + val threadSummaryLatestEvent: Event? = null, val lastMessageTimestamp: Long? = null, var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE, val isThread: Boolean = false, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 9c420e81fd..c3302f5ccb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -114,7 +114,7 @@ internal object EventMapper { ) }, threadNotificationState = eventEntity.threadNotificationState, - threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary(), + threadSummaryLatestEvent = eventEntity.threadSummaryLatestMessage?.root?.asDomain(), lastMessageTimestamp = eventEntity.threadSummaryLatestMessage?.root?.originServerTs ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 2cdc1a0d90..5b1f17cfe2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -32,6 +32,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.ui.list.GenericHeaderItem_ import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Content @@ -45,6 +46,7 @@ class SearchResultController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, private val dateFormatter: VectorDateFormatter, + private val displayableEventFormatter: DisplayableEventFormatter, private val userPreferencesProvider: UserPreferencesProvider ) : TypedEpoxyController() { @@ -125,6 +127,7 @@ class SearchResultController @Inject constructor( .sender(eventAndSender.sender ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem()) .threadDetails(event.threadDetails) + .threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString()) .areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled()) .listener { listener?.onItemClicked(eventAndSender.event) } .let { result.add(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt index 2ec786fab2..3e141ab0e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt @@ -42,6 +42,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var spannable: EpoxyCharSequence @EpoxyAttribute var sender: MatrixItem? = null @EpoxyAttribute var threadDetails: ThreadDetails? = null + @EpoxyAttribute var threadSummaryFormatted: String? = null @EpoxyAttribute var areThreadMessagesEnabled: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null @@ -60,8 +61,7 @@ abstract class SearchResultItem : VectorEpoxyModel() { if (it.isRootThread) { showThreadSummary(holder) holder.threadSummaryCounterTextView.text = it.numberOfThreads.toString() - holder.threadSummaryInfoTextView.text = it.threadSummaryLatestTextMessage.orEmpty() - + holder.threadSummaryInfoTextView.text = threadSummaryFormatted.orEmpty() val userId = it.threadSummarySenderInfo?.userId ?: return@let val displayName = it.threadSummarySenderInfo?.displayName val avatarUrl = it.threadSummarySenderInfo?.avatarUrl diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index d5f3a74e4e..d4a6f2ee87 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -24,9 +24,11 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.html.EventHtmlRenderer import me.gujun.android.span.span import org.commonmark.node.Document +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -120,14 +122,14 @@ class DisplayableEventFormatter @Inject constructor( EventType.CALL_CANDIDATES -> { span { } } - EventType.POLL_START -> { + EventType.POLL_START -> { timelineEvent.root.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question ?: stringProvider.getString(R.string.sent_a_poll) } - EventType.POLL_RESPONSE -> { + EventType.POLL_RESPONSE -> { stringProvider.getString(R.string.poll_response_room_list_preview) } - EventType.POLL_END -> { + EventType.POLL_END -> { stringProvider.getString(R.string.poll_end_room_list_preview) } else -> { @@ -139,6 +141,98 @@ class DisplayableEventFormatter @Inject constructor( } } + fun formatThreadSummary( + event: Event?, + latestEdition: String? = null): CharSequence { + event ?: return "" + + // There event have been edited + if (latestEdition != null) { + return run { + val localFormattedBody = htmlRenderer.get().parse(latestEdition) as Document + val renderedBody = htmlRenderer.get().render(localFormattedBody) ?: latestEdition + renderedBody + } + } + + // The event have been redacted + if (event.isRedacted()) { + return noticeEventFormatter.formatRedactedEvent(event) + } + + // The event is encrypted + if (event.isEncrypted() && + event.mxDecryptionResult == null) { + return stringProvider.getString(R.string.encrypted_message) + } + + return when (event.getClearType()) { + EventType.MESSAGE -> { + (event.getClearContent().toModel() as? MessageContent)?.let { messageContent -> + when (messageContent.msgType) { + MessageType.MSGTYPE_TEXT -> { + val body = messageContent.getTextDisplayableContent() + if (messageContent is MessageTextContent && messageContent.matrixFormattedBody.isNullOrBlank().not()) { + val localFormattedBody = htmlRenderer.get().parse(body) as Document + val renderedBody = htmlRenderer.get().render(localFormattedBody) ?: body + renderedBody + } else { + body + } + } + MessageType.MSGTYPE_VERIFICATION_REQUEST -> { + stringProvider.getString(R.string.verification_request) + } + MessageType.MSGTYPE_IMAGE -> { + stringProvider.getString(R.string.sent_an_image) + } + MessageType.MSGTYPE_AUDIO -> { + if ((messageContent as? MessageAudioContent)?.voiceMessageIndicator != null) { + stringProvider.getString(R.string.sent_a_voice_message) + } else { + stringProvider.getString(R.string.sent_an_audio_file) + } + } + MessageType.MSGTYPE_VIDEO -> { + stringProvider.getString(R.string.sent_a_video) + } + MessageType.MSGTYPE_FILE -> { + stringProvider.getString(R.string.sent_a_file) + } + MessageType.MSGTYPE_LOCATION -> { + stringProvider.getString(R.string.sent_location) + } + else -> { + messageContent.body + } + } + } ?: span { } + } + EventType.STICKER -> { + stringProvider.getString(R.string.send_a_sticker) + } + EventType.REACTION -> { + event.getClearContent().toModel()?.relatesTo?.let { + emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key)) + } ?: span { } + } + EventType.POLL_START -> { + event.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question + ?: stringProvider.getString(R.string.sent_a_poll) + } + EventType.POLL_RESPONSE -> { + stringProvider.getString(R.string.poll_response_room_list_preview) + } + EventType.POLL_END -> { + stringProvider.getString(R.string.poll_end_room_list_preview) + } + else -> { + span { + } + } + } + } + private fun simpleFormat(senderName: String, body: CharSequence, appendAuthor: Boolean): CharSequence { return if (appendAuthor) { span { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 845b765101..ef42e32a76 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -22,6 +22,7 @@ import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import org.matrix.android.sdk.api.session.threads.ThreadDetails @@ -32,6 +33,7 @@ class MessageItemAttributesFactory @Inject constructor( private val messageColorProvider: MessageColorProvider, private val avatarSizeProvider: AvatarSizeProvider, private val stringProvider: StringProvider, + private val displayableEventFormatter: DisplayableEventFormatter, private val preferencesProvider: UserPreferencesProvider, private val emojiCompatFontProvider: EmojiCompatFontProvider) { @@ -59,6 +61,7 @@ class MessageItemAttributesFactory @Inject constructor( readReceiptsCallback = callback, emojiTypeFace = emojiCompatFontProvider.typeface, decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message), + threadSummaryFormatted = displayableEventFormatter.formatThreadSummary(threadDetails?.threadSummaryLatestEvent).toString(), threadDetails = threadDetails, areThreadMessagesEnabled = preferencesProvider.areThreadMessagesEnabled() ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 9e8f86c26e..bad29bd444 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -115,7 +115,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem attributes.threadDetails?.let { threadDetails -> holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() - holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage ?: attributes.decryptionErrorMessage + holder.threadSummaryInfoTextView.text = attributes.threadSummaryFormatted ?: attributes.decryptionErrorMessage val userId = threadDetails.threadSummarySenderInfo?.userId ?: return@let val displayName = threadDetails.threadSummarySenderInfo?.displayName @@ -183,6 +183,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, val emojiTypeFace: Typeface? = null, val decryptionErrorMessage: String? = null, + val threadSummaryFormatted: String? = null, val threadDetails: ThreadDetails? = null, val areThreadMessagesEnabled: Boolean = false ) : AbsBaseMessageItem.Attributes { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index d3a5497d63..aeef69c6dc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -17,11 +17,11 @@ package im.vector.app.features.home.room.threads.list.viewmodel import com.airbnb.epoxy.EpoxyController -import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.threads.list.model.threadListItem import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary @@ -35,6 +35,7 @@ class ThreadListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, private val dateFormatter: VectorDateFormatter, + private val displayableEventFormatter: DisplayableEventFormatter, private val session: Session ) : EpoxyController() { @@ -70,9 +71,18 @@ class ThreadListController @Inject constructor( } ?.forEach { threadSummary -> val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST) - val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message) - val rootThreadEdition = threadSummary.threadEditions.rootThreadEdition - val latestThreadEdition = threadSummary.threadEditions.latestThreadEdition + val lastMessageFormatted = threadSummary.let { + displayableEventFormatter.formatThreadSummary( + event = it.latestEvent, + latestEdition = it.threadEditions.latestThreadEdition + ).toString() + } + val rootMessageFormatted = threadSummary.let { + displayableEventFormatter.formatThreadSummary( + event = it.rootEvent, + latestEdition = it.threadEditions.rootThreadEdition + ).toString() + } threadListItem { id(threadSummary.rootEvent?.eventId) avatarRenderer(host.avatarRenderer) @@ -82,8 +92,8 @@ class ThreadListController @Inject constructor( rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false) // TODO refactor notifications that with the new thread summary threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE) - rootMessage(rootThreadEdition ?: threadSummary.rootEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage) - lastMessage(latestThreadEdition ?: threadSummary.latestEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage) + rootMessage(rootMessageFormatted) + lastMessage(lastMessageFormatted) lastMessageCounter(threadSummary.numberOfThreads.toString()) lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull()) itemClickListener { @@ -112,8 +122,18 @@ class ThreadListController @Inject constructor( } ?.forEach { timelineEvent -> val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST) - val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message) val lastRootThreadEdition = timelineEvent.root.threadDetails?.lastRootThreadEdition + val lastMessageFormatted = timelineEvent.root.threadDetails?.threadSummaryLatestEvent.let { + displayableEventFormatter.formatThreadSummary( + event = it, + ).toString() + } + val rootMessageFormatted = timelineEvent.root.let { + displayableEventFormatter.formatThreadSummary( + event = it, + latestEdition = lastRootThreadEdition + ).toString() + } threadListItem { id(timelineEvent.eventId) avatarRenderer(host.avatarRenderer) @@ -122,8 +142,8 @@ class ThreadListController @Inject constructor( date(date) rootMessageDeleted(timelineEvent.root.isRedacted()) threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE) - rootMessage(lastRootThreadEdition ?: timelineEvent.root.getDecryptedTextSummary() ?: decryptionErrorMessage) - lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage ?: decryptionErrorMessage) + rootMessage(rootMessageFormatted) + lastMessage(lastMessageFormatted) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) itemClickListener { From ab2001cd7f8f97440a57a7f85e28723c32bc20a2 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 2 Mar 2022 17:45:27 +0300 Subject: [PATCH 0035/1430] Create a custom audio waveform view. --- .../main/res/values/styles_voice_message.xml | 16 +- .../detail/composer/VoiceMessageHelper.kt | 4 +- .../composer/voice/VoiceMessageViews.kt | 12 +- .../timeline/factory/MessageItemFactory.kt | 4 +- .../helper/VoiceMessagePlaybackTracker.kt | 23 +- .../detail/timeline/item/MessageVoiceItem.kt | 40 ++-- .../app/features/voice/AudioWaveformView.kt | 199 ++++++++++++++++++ .../layout/item_timeline_event_voice_stub.xml | 2 +- .../layout/view_voice_message_recorder.xml | 2 +- .../main/res/values/audio_waveform_attr.xml | 22 ++ 10 files changed, 287 insertions(+), 37 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt create mode 100644 vector/src/main/res/values/audio_waveform_attr.xml diff --git a/library/ui-styles/src/main/res/values/styles_voice_message.xml b/library/ui-styles/src/main/res/values/styles_voice_message.xml index 2e87353303..81d2e7581d 100644 --- a/library/ui-styles/src/main/res/values/styles_voice_message.xml +++ b/library/ui-styles/src/main/res/values/styles_voice_message.xml @@ -2,14 +2,14 @@ \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index 735d356476..f9dfecd1f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -221,7 +221,9 @@ class VoiceMessageHelper @Inject constructor( private fun onPlaybackTick(id: String) { if (mediaPlayer?.isPlaying.orFalse()) { val currentPosition = mediaPlayer?.currentPosition ?: 0 - playbackTracker.updateCurrentPlaybackTime(id, currentPosition) + val totalDuration = mediaPlayer?.duration ?: 0 + val percentage = currentPosition.toFloat() / totalDuration + playbackTracker.updateCurrentPlaybackTime(id, currentPosition, percentage) } else { playbackTracker.stopPlayback(id) stopPlaybackTicker() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 09284ea5fc..8adecaad6e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt @@ -27,7 +27,6 @@ import androidx.core.view.doOnLayout import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams -import com.visualizer.amplitude.AudioRecordView import im.vector.app.R import im.vector.app.core.extensions.setAttributeBackground import im.vector.app.core.extensions.setAttributeTintedBackground @@ -37,6 +36,8 @@ import im.vector.app.databinding.ViewVoiceMessageRecorderBinding import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker +import im.vector.app.features.themes.ThemeUtils +import im.vector.app.features.voice.AudioWaveformView class VoiceMessageViews( private val resources: Resources, @@ -284,7 +285,7 @@ class VoiceMessageViews( hideRecordingViews(RecordingUiState.Idle) views.voiceMessageMicButton.isVisible = true views.voiceMessageSendButton.isVisible = false - views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() } + views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.clear() } } fun renderPlaying(state: VoiceMessagePlaybackTracker.Listener.State.Playing) { @@ -292,11 +293,15 @@ class VoiceMessageViews( views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_pause_voice_message) val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong()) views.voicePlaybackTime.text = formattedTimerText + val waveformColorIdle = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_quaternary) + val waveformColorPlayed = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_secondary) + views.voicePlaybackWaveform.updateColors(state.percentage, waveformColorPlayed, waveformColorIdle) } fun renderIdle() { views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_play_voice_message) + views.voicePlaybackWaveform.summarize() } fun renderToast(message: String) { @@ -327,8 +332,9 @@ class VoiceMessageViews( fun renderRecordingWaveform(amplitudeList: Array) { views.voicePlaybackWaveform.doOnLayout { waveFormView -> + val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_secondary) amplitudeList.iterator().forEach { - (waveFormView as AudioRecordView).update(it) + (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 0c836748c8..da97cf6984 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -73,6 +73,7 @@ import im.vector.app.features.location.toLocationData import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voice.AudioWaveformView import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -688,8 +689,7 @@ class MessageItemFactory @Inject constructor( return this ?.filterNotNull() ?.map { - // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec - it * 22760 / 1024 + it * AudioWaveformView.MAX_FFT / 1024 } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt index c6204bff1c..076c05b9c4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt @@ -70,7 +70,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() { fun startPlayback(id: String) { val currentPlaybackTime = getPlaybackTime(id) - val currentState = Listener.State.Playing(currentPlaybackTime) + val currentPercentage = getPercentage(id) + val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage) setState(id, currentState) // Pause any active playback states @@ -87,15 +88,16 @@ class VoiceMessagePlaybackTracker @Inject constructor() { fun pausePlayback(id: String) { val currentPlaybackTime = getPlaybackTime(id) - setState(id, Listener.State.Paused(currentPlaybackTime)) + val currentPercentage = getPercentage(id) + setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage)) } fun stopPlayback(id: String) { setState(id, Listener.State.Idle) } - fun updateCurrentPlaybackTime(id: String, time: Int) { - setState(id, Listener.State.Playing(time)) + fun updateCurrentPlaybackTime(id: String, time: Int, percentage: Float) { + setState(id, Listener.State.Playing(time, percentage)) } fun updateCurrentRecording(id: String, amplitudeList: List) { @@ -113,6 +115,15 @@ class VoiceMessagePlaybackTracker @Inject constructor() { } } + fun getPercentage(id: String): Float { + return when (val state = states[id]) { + is Listener.State.Playing -> state.percentage + is Listener.State.Paused -> state.percentage + /* Listener.State.Idle, */ + else -> 0f + } + } + fun clear() { listeners.forEach { it.value.onUpdate(Listener.State.Idle) @@ -131,8 +142,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() { sealed class State { object Idle : State() - data class Playing(val playbackTime: Int) : State() - data class Paused(val playbackTime: Int) : State() + data class Playing(val playbackTime: Int, val percentage: Float) : State() + data class Paused(val playbackTime: Int, val percentage: Float) : State() data class Recording(val amplitudeList: List) : State() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index e9f728d976..82400a431d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -26,7 +26,6 @@ import android.widget.TextView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import com.visualizer.amplitude.AudioRecordView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder @@ -34,6 +33,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.themes.ThemeUtils +import im.vector.app.features.voice.AudioWaveformView @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageVoiceItem : AbsMessageItem() { @@ -78,11 +78,15 @@ abstract class MessageVoiceItem : AbsMessageItem() { holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener) + val waveformColorIdle = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quaternary) + val waveformColorPlayed = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary) + holder.voicePlaybackWaveform.post { - holder.voicePlaybackWaveform.recreate() + holder.voicePlaybackWaveform.clear() waveform.forEach { amplitude -> - holder.voicePlaybackWaveform.update(amplitude) + holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle)) } + holder.voicePlaybackWaveform.summarize() } val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) { @@ -93,33 +97,39 @@ abstract class MessageVoiceItem : AbsMessageItem() { holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint) holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) } - voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { - override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { - when (state) { - is VoiceMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder) - is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state) - is VoiceMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state) + // Don't track and don't try to update UI before view is present + holder.view.post { + voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener { + override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { + when (state) { + is VoiceMessagePlaybackTracker.Listener.State.Idle -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed) + is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed) + is VoiceMessagePlaybackTracker.Listener.State.Paused -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed) + } } - } - }) + }) + } } - private fun renderIdleState(holder: Holder) { + private fun renderIdleState(holder: Holder, idleColor: Int, playedColor: Int) { holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) holder.voicePlaybackTime.text = formatPlaybackTime(duration) + holder.voicePlaybackWaveform.updateColors(0f, playedColor, idleColor) } - private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) { + private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing, idleColor: Int, playedColor: Int) { holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_voice_message) holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor) } - private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused) { + private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused, idleColor: Int, playedColor: Int) { holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play) holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message) holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime) + holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor) } private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong()) @@ -138,7 +148,7 @@ abstract class MessageVoiceItem : AbsMessageItem() { val voiceLayout by bind(R.id.voiceLayout) val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton) val voicePlaybackTime by bind(R.id.voicePlaybackTime) - val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform) + val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform) val progressLayout by bind(R.id.messageFileUploadProgressLayout) } diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt new file mode 100644 index 0000000000..9ba7597e60 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2022 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.app.features.voice + +import android.content.Context +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import im.vector.app.R +import kotlin.math.max +import kotlin.random.Random + +class AudioWaveformView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private enum class Alignment(var value: Int) { + CENTER(0), + BOTTOM(1), + TOP(2) + } + + private enum class Flow(var value: Int) { + LTR(0), + RTL(1) + } + + data class FFT(val value: Float, var color: Int) + + private fun Int.dp() = this * Resources.getSystem().displayMetrics.density + + // Configuration fields + private var alignment = Alignment.CENTER + private var flow = Flow.LTR + private var verticalPadding = 4.dp() + private var horizontalPadding = 4.dp() + private var barWidth = 2.dp() + private var barSpace = 1.dp() + private var barMinHeight = 1.dp() + private var isBarRounded = true + + private val rawFftList = mutableListOf() + private var visibleBarHeights = mutableListOf() + + private val barPaint = Paint() + + init { + attrs?.let { + context + .theme + .obtainStyledAttributes( + attrs, + R.styleable.AudioWaveformView, + 0, + 0 + ) + .apply { + alignment = Alignment.values().find { it.value == getInt(R.styleable.AudioWaveformView_alignment, alignment.value) }!! + flow = Flow.values().find { it.value == getInt(R.styleable.AudioWaveformView_flow, alignment.value) }!! + verticalPadding = getDimension(R.styleable.AudioWaveformView_verticalPadding, verticalPadding) + horizontalPadding = getDimension(R.styleable.AudioWaveformView_horizontalPadding, horizontalPadding) + barWidth = getDimension(R.styleable.AudioWaveformView_barWidth, barWidth) + barSpace = getDimension(R.styleable.AudioWaveformView_barSpace, barSpace) + barMinHeight = getDimension(R.styleable.AudioWaveformView_barMinHeight, barMinHeight) + isBarRounded = getBoolean(R.styleable.AudioWaveformView_isBarRounded, isBarRounded) + setWillNotDraw(false) + barPaint.isAntiAlias = true + } + .apply { recycle() } + .also { + barPaint.strokeWidth = barWidth + barPaint.strokeCap = if (isBarRounded) Paint.Cap.ROUND else Paint.Cap.BUTT + } + } + } + + fun initialize(fftList: List) { + handleNewFftList(fftList) + invalidate() + } + + fun add(fft: FFT) { + handleNewFftList(listOf(fft)) + invalidate() + } + + fun summarize() { + if (rawFftList.isEmpty()) return + + val maxVisibleBarCount = getMaxVisibleBarCount() + val summarizedFftList = rawFftList.summarize(maxVisibleBarCount) + clear() + handleNewFftList(summarizedFftList) + invalidate() + } + + fun updateColors(limitPercentage: Float, colorBefore: Int, colorAfter: Int) { + val size = visibleBarHeights.size + val limitIndex = (size * limitPercentage).toInt() + visibleBarHeights.forEachIndexed { index, fft -> + fft.color = if (index < limitIndex) { + colorBefore + } else { + colorAfter + } + } + invalidate() + } + + fun clear() { + rawFftList.clear() + visibleBarHeights.clear() + } + + private fun List.summarize(target: Int): List { + val result = mutableListOf() + if (size <= target) { + result.addAll(this) + val missingItemCount = target - size + repeat(missingItemCount) { + val index = Random.nextInt(result.size) + result.add(index, result[index]) + } + } else { + val step = (size.toDouble() - 1) / (target - 1) + var index = 0.0 + while (index < size) { + result.add(get(index.toInt())) + index += step + } + } + return result + } + + private fun handleNewFftList(fftList: List) { + val maxVisibleBarCount = getMaxVisibleBarCount() + fftList.forEach { fft -> + rawFftList.add(fft) + val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight) + visibleBarHeights.add(FFT(barHeight, fft.color)) + if (visibleBarHeights.size > maxVisibleBarCount) { + visibleBarHeights = visibleBarHeights.subList(visibleBarHeights.size - maxVisibleBarCount, visibleBarHeights.size) + } + } + } + + private fun getMaxVisibleBarCount() = ((width - horizontalPadding * 2) / (barWidth + barSpace)).toInt() + + private fun drawBars(canvas: Canvas) { + var currentX = horizontalPadding + visibleBarHeights.forEach { + barPaint.color = it.color + // TODO. Support flow + when (alignment) { + Alignment.BOTTOM -> { + val startY = height - verticalPadding + val stopY = startY - it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + Alignment.CENTER -> { + val startY = (height - it.value) / 2 + val stopY = startY + it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + Alignment.TOP -> { + val startY = verticalPadding + val stopY = startY + it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + } + currentX += barWidth + barSpace + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawBars(canvas) + } + + companion object { + private const val MAX_FFT = 32760f + } +} diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml index a180afbf8e..0fad714bd4 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml @@ -40,7 +40,7 @@ app:layout_constraintTop_toTopOf="@id/voicePlaybackControlButton" tools:text="0:23" /> - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 243a714586b13afeaa2432344687bddbf9c25658 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 2 Mar 2022 17:46:09 +0300 Subject: [PATCH 0036/1430] Remove 3rd party waveform library. --- library/ui-styles/build.gradle | 2 -- vector/build.gradle | 1 - vector/src/main/assets/open_source_licenses.html | 5 ----- .../java/im/vector/app/features/voice/AudioWaveformView.kt | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index cee58414c7..0ac513b252 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -60,6 +60,4 @@ dependencies { implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' // dialpad dimen implementation 'im.dlg:android-dialer:1.2.5' - // AudioRecordView attr - implementation 'com.github.Armen101:AudioRecordView:1.0.5' } \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index c6a2636acf..d58118eb24 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -416,7 +416,6 @@ dependencies { implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' implementation 'com.github.hyuwah:DraggableView:1.0.0' - implementation 'com.github.Armen101:AudioRecordView:1.0.5' // Custom Tab implementation 'androidx.browser:browser:1.4.0' diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 2c25606f57..0bead1f826 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -437,11 +437,6 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2017-present, dialog LLC <info@dlg.im> -
  • - Armen101 / AudioRecordView -
    - Copyright 2019 Armen Gevorgyan -
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    index 9ba7597e60..768635b2f7 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -194,6 +194,6 @@ class AudioWaveformView @JvmOverloads constructor(
         }
     
         companion object {
    -        private const val MAX_FFT = 32760f
    +        const val MAX_FFT = 32760
         }
     }
    
    From 4ca2a4e8b14b59cc3db02bf8fa9591ca1ef21aab Mon Sep 17 00:00:00 2001
    From: libexus 
    Date: Tue, 1 Mar 2022 15:38:06 +0000
    Subject: [PATCH 0037/1430] Translated using Weblate (German)
    
    Currently translated at 99.8% (2153 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
    ---
     vector/src/main/res/values-de/strings.xml | 78 +++++++----------------
     1 file changed, 23 insertions(+), 55 deletions(-)
    
    diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
    index bf72827085..7a8ac9c71b 100644
    --- a/vector/src/main/res/values-de/strings.xml
    +++ b/vector/src/main/res/values-de/strings.xml
    @@ -39,7 +39,6 @@
         Raumeinladung
         %1$s und %2$s
         Leerer Raum
    -
         %s hat diesen Raum aufgewertet.
         Sende eine Nachricht…
         Erste Synchronisation:
    @@ -244,7 +243,6 @@
         Löschen
         Umbenennen
         Inhalt melden
    -
         oder
         Einladen
         Abmelden
    @@ -267,7 +265,6 @@
         Nur Matrix-Kontakte
         Keine Ergebnisse
         Räume
    -
         Logdateien übermitteln
         Absturzberichte übermitteln
         Screenshot übermitteln
    @@ -298,7 +295,6 @@
         Dieser Homeserver möchte sicherstellen, dass du kein Roboter bist
         Die E-Mail-Adresse, die mit deinem Account verknüpft ist, muss eingegeben werden.
         Verifizierung der E-Mail-Adresse ist fehlgeschlagen. Stelle sicher, dass du den Link in der E-Mail geöffnet hast
    -
         Bitte eine gültige URL eingeben
         Fehlerhaftes JSON
         Enthielt kein gültiges JSON
    @@ -314,13 +310,10 @@
         Anruf aktiv…
         Die Gegenseite hat den Anruf nicht angenommen.
         Information
    -
         ${app_name} benötigt die Berechtigung, auf dein Mikrofon zugreifen zu können, um (Sprach-)Anrufe tätigen zu können.
    -
         ${app_name} benötigt die Berechtigung, auf Kamera und Mikrofon zu zugreifen, um Video-Anrufe durchzuführen.
     \n
     \nBitte erlaube den Zugriff im nächsten Dialog, um den Anruf durchzuführen.
    -
         Ja
         Nein
         Fortsetzen
    @@ -328,7 +321,6 @@
         Betreten
         Ablehnen
         Zu ungelesenen Nachrichten springen
    -
         Raum verlassen
         Raum wirklich verlassen\?
         DIREKT-CHATS
    @@ -341,8 +333,8 @@
         Du wirst diese Änderung nicht rückgängig machen können, da die Person dieselbe Berechtigungsstufe wie du erhalten wird.
     \nBist du sicher\?
         %s schreibt…
    -    %1$s & %2$s schreiben…
    -    %1$s, %2$s & andere schreiben…
    +    %1$s und %2$s schreiben…
    +    %1$s, %2$s und andere schreiben…
         Du bist nicht berechtigt, in diesen Raum zu schreiben.
         Vertrauen
         Nicht vertrauen
    @@ -355,7 +347,6 @@
         Das Zertifikat unterscheidet sich von dem Zertifikat, dem dein Gerät ursprünglich vertraut hat. Dies ist SEHR UNGEWÖHNLICH. Es wird empfohlen, dass du dieses neue Zertifikat NICHT AKZEPTIERST.
         Das Zertifikat hat sich von einem ursprünglich vertrauenswürdigem Zertifikat in ein nicht vertrauenswürdiges Zertifikat geändert. Eventuell wurde das Zertifikat des Servers erneuert. Bitte erkundige dich beim Server-Administrator, welcher Fingerprint als vertrauenswürdig gilt.
         Akzeptiere das Zertifikat nur dann, wenn der Server-Administrator einen Fingerprint veröffentlicht hat, der mit dem obigen übereinstimmt.
    -
         Suchen
         Raummitglieder filtern
         Keine Suchergebnisse
    @@ -400,7 +391,6 @@
         Öffentlichen Namen aktualisieren
         Zuletzt gesehen
         %1$s @ %2$s
    -
         Authentifizierung
         Angemeldet als
         Heimserver
    @@ -441,7 +431,6 @@
         Exportieren
         Passphrase eingeben
         Passphrase bestätigen
    -
         Ende-zu-Ende-Raumschlüssel importieren
         Raumschlüssel importieren
         Schlüssel aus lokaler Datei importieren
    @@ -453,7 +442,6 @@
         Bestätigen
         Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige:
         Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert.
    -
         Raumverzeichnis auswählen
         Server-Name
         Alle Räume auf dem %s-Server
    @@ -531,7 +519,6 @@
         Community-Avatare
         Schütteln, um einen Fehler zu melden
         Mitglieder auflisten
    -
         
             %d Mitglied
             %d Mitglieder
    @@ -540,13 +527,10 @@
             %d neue Nachricht
             %d neue Nachrichten
         
    -
    -
         
             %d ungelesene Nachricht
             %d ungelesene Nachrichten
         
    -
         
             %d Raum
             %d Räume
    @@ -606,16 +590,10 @@
         Die Konversation wird hier fortgesetzt
         Dieser Raum ist die Fortsetzung einer anderen Konversation
         Klicke hier um die älteren Nachrichten zu sehen
    -
    -
    -
    -
         
             %d ausgewählt
             %d ausgewählt
         
    -
    -
         Systembenachrichtigungen
         kontaktiere deinen Service-Administrator
         Dieser Homeserver hat eine seiner Ressourcen-Grenzen erreicht, sodass einige Nutzer sich nicht anmelden können.
    @@ -707,7 +685,6 @@
         Token-Registrierung
         Wenn ein Benutzer ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen.
         Ignoriere Optimierungen
    -
         Keine validen Google-Play-Dienste gefunden. Benachrichtigungen könnten nicht richtig funktionieren.
         Videogespräch aktiv…
         Schlüsselsicherung
    @@ -756,7 +733,6 @@
         Nachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der Empfänger haben die Schlüssel um diese Nachrichten zu lesen.
     \n
     \nSichere deine Schlüssel, um sie nicht zu verlieren.
    -
         Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen.
         Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst.
         Rufe Backup-Version ab…
    @@ -817,7 +793,6 @@
         Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort auf, wie z.B. einem Passwortmanager (oder Tresor) auf
         Ich habe eine Kopie angefertigt
         Teilen
    -
         Verliere nie wieder verschlüsselte Nachrichten
         Benutze Schlüsselsicherung
         Neue sichere Schlüssel für Nachrichten
    @@ -839,7 +814,6 @@
         Nachricht mit Eingabetaste senden
         Eingabetaste der Bildschirmtastatur schickt die Nachricht ab, statt einen Zeilenumbruch zu erzeugen
         Das Passwort ist ungültig
    -
         Medien
         Standard-Komprimierung
         Wählen
    @@ -879,7 +853,6 @@
         Überprüfe Sicherungsstatus
         Verifiziert!
         Verstanden
    -
         Verifizierungsanfrage
         %s möchte deine Sitzung verifizieren
         Unbekannter Fehler
    @@ -949,7 +922,6 @@
         Link in die Zwischenablage kopiert
         Raum erstellen…
         Bearbeitungsverlauf anzeigen
    -
         E2E-Schlüssel aus der Datei \"%1$s\" importieren.
         Vielen Dank, der Vorschlag wurde erfolgreich gesendet
         Der Vorschlag konnte nicht gesendet werden (%s)
    @@ -986,7 +958,6 @@
         Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast.
         Gib einen neuen Identitätsserver ein
         Konnte keine Verbindung zum Homeserver herstellen
    -
         Dies ist keine Adresse eines Matrixservers
         Kann Homeserver nicht unter dieser URL erreichen. Bitte überprüfen
         Hintergrund-Synchronisierungsmodus
    @@ -994,7 +965,6 @@
     \nAbhängig vom Ressourcen-Status deines Geräts kann dein System die Synchronisierung verschieben.
         ${app_name} wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar).
     \nDies wird Funk- und Akkunutzung beeinflussen. Es wird eine permanente Benachrichtigung geben, die sagt, dass ${app_name} auf Ereignisse lauscht.
    -
         Integrationen
         Benutze einen Integrationsmanager um Bots, Brücken, Widgets und Stickerpakete zu verwalten.
     \nIntegrationsmanager erhalten Rauminformationen und können so Widgets verändern, Einladungen senden und in deinem Namen Berechtigungslevel setzen.
    @@ -1078,7 +1048,6 @@
         Dieser Inhalt wurde als unangebracht gemeldet.
     \n
     \nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden.
    -
         Nutzer ignorieren
         Alle Nachrichten (laut)
         Alle Nachrichten
    @@ -1101,13 +1070,13 @@
         Premium-Hosting für Organisationen
         Mehr erfahren
         Andere
    -    Benutzerdefinierte & erweiterte Einstellungen
    +    Benutzerdefinierte und erweiterte Einstellungen
         Fortfahren
         Eine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzern gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst.
         Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitätsserver %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören.
         Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden zu werden.
         Zu teilende Daten nicht verarbeitbar
    -    Erweitere & individualisiere dein Benutzererlebnis
    +    Erweitere und individualisiere dein Benutzererlebnis
         Mit %1$s verbinden
         Mit Element Matrix Services verbinden
         Mit einem individuellen Server verbinden
    @@ -1271,7 +1240,6 @@
         %s verifizieren
         %s verifiziert
         Warte auf %s…
    -
         Nachrichten in diesem Raum sind nicht Ende-zu-Ende-verschlüsselt.
         Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt.
     \n
    @@ -1295,7 +1263,7 @@
         Nutzer
         Admin in %1$s
         Moderation in %1$s
    -    Springen & als gelesen markieren
    +    Springen und als gelesen markieren
         ${app_name} kann keine Ereignisse vom Typ \'%1$s\'
         ${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßen
         Nicht ignorieren
    @@ -1392,7 +1360,7 @@
     \n- Dieses Gerät, oder das andere Gerät
     \n- Die Internetverbindung, die von den Geräten genutzt wird
     \n
    -\nWir empfehlen dir dein Passwort & Wiederherstellungsschlüssel in den Einstellungen sofort zu ändern.
    +\nWir empfehlen dir dein Passwort und den Wiederherstellungsschlüssel in den Einstellungen sofort zu ändern.
         Verifizierung abgebrochen. Du kannst sie erneut starten.
         Verifizierung abgebrochen
         Gib dein %s ein um fortzufahren.
    @@ -1409,7 +1377,7 @@
         Synchronisiere Benutzerschlüssel
         Synchronisiere selbstsignierenden Schlüssel
         Richte Schlüsselbackup ein
    -    Deine %2$s & %1$s sind nun eingerichtet.
    +    Deine %2$s und %1$s sind nun eingerichtet.
     \n
     \nBewahre sie sicher auf! Du wirst sie benötigen, um verschlüsselte Nachrichten und sichere Informationen freizuschalten, wenn du alle deine aktive Sitzungen verlierst.
         Speichere ihn auf einem USB-Stick oder auf einem Sicherungslaufwerk
    @@ -1426,7 +1394,7 @@
         Verschlüsselung ist nicht aktiviert
         Raumupgrades
         Verschlüsselung aktiviert
    -    Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr & verifiziere Benutzer in deren Profil.
    +    Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr und verifiziere Benutzer in deren Profil.
         Die Verschlüsselung in diesem Raum wird nicht unterstützt
         Warte auf %s…
         Fehlerbehebung
    @@ -1435,12 +1403,11 @@
         Fast geschafft! Warte auf Bestätigung…
         Verschlüsselte Direktnachrichten
         Nachricht…
    -    Verifiziere dich & andere, um eure Chats zu schützen
    +    Verifiziere dich und andere, um eure Chats zu schützen
         Gib zum Fortfahren deinen %s ein
         Datei benutzen
         Dies ist kein gültiger Wiederherstellungsschlüssel
         Bitte gib deinen Wiederherstellungsschlüssel ein
    -
         Verschlüsselungsupgrade verfügbar
         Überprüfe Wiederherstellungsschlüssel
         Überprüfe Sicherungsstatus (%s)
    @@ -1472,7 +1439,7 @@
         Unverschlüsselt
         Verschlüsselt von einem unbekannten Gerät
         Überprüfe, wo du angemeldet bist
    -    Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto & deine Nachrichten sicher sind
    +    Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto und deine Nachrichten sicher sind
         Bestätige neue Anmeldung zu deinem Konto: %1$s
         Verifiziere manuell mit einem Text
         Anmeldung verifizieren
    @@ -1543,7 +1510,7 @@
         Backup einrichten
         Backup zurücksetzen
         Auf diesem Gerät einrichten
    -    Verlust verschlüsselter Nachrichten & Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden.
    +    Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden.
         Generiere einen neuen Sicherheitsschlüssel oder setze eine neue Sicherheitspassphrase für dein existierendes Backup.
         Dieses wird deinen aktuellen Schlüssel oder deine aktuelle Phrase ersetzen.
         Integrationen sind deaktiviert
    @@ -1589,9 +1556,9 @@
         Dein Serveradministrator hat in privaten Räumen und Direktnachrichten Ende-zu-Ende-Verschlüsselung standardmäßig deaktiviert.
         Flugzeugmodus ist aktiv
         Gib eine Sicherheitsphrase ein, die nur du kennst. Diese wird benutzt um deine Daten auf dem Server geheim zu halten.
    -    Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten & Daten verlieren.
    +    Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten und Daten verlieren.
     \n
    -\nDu kannst auch ein Backup einrichten & deine Schlüssel in den Einstellungen verwalten.
    +\nDu kannst auch ein Backup einrichten und deine Schlüssel in den Einstellungen verwalten.
         Du hast den Raum erstellt und konfiguriert.
         Dieser Account ist deaktiviert worden.
         Konnte Mediendatei nicht speichern
    @@ -1620,7 +1587,7 @@
         Stoppe Kamera
         Starte Kamera
         Backup
    -    Verlust verschlüsselter Nachrichten & Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden.
    +    Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden.
         Sicherheitsschlüssel benutzen
         Generiere einen Sicherheitsschlüssel, welcher z.B. in einem Passwortmanager oder in einem Tresor sicher aufbewahrt werden sollte.
         Eine Sicherheitsphrase benutzen
    @@ -1786,7 +1753,7 @@
         Raumname
         Prüfung exportieren
         Direktnachricht
    -    Geschichte der Anfragen von Schlüsselfreigaben senden
    +    Verlauf der Anfragen von Schlüsselfreigaben senden
         Keine weiteren Ergebnisse
         Starte die Diskussion
         Autorisieren
    @@ -1841,7 +1808,6 @@
         Aktivieren, wenn der Raum nur von Mitgliedern deines Homeservers zur internen Kommunikation verwendet wird. Das kann später nicht mehr geändert werden.
         Begrenze Zugang zu diesem Raum (für immer!) auf Mitglieder von %s
         %1$d von %2$d
    -
         Keine Vorschau für diesen Raum verfügbar. Willst du direkt beitreten\?
         Der Raum ist gerade nicht zugänglich.
     \nVersuche es später nochmal, oder bitte einen Raum-Admin um Hilfe.
    @@ -1910,8 +1876,6 @@
         Beim Weiterleiten des Anrufs ist ein Fehler aufgetreten
         Weiterleiten
         Verbinden
    -
    -
         Aktiver Anruf (%1$s)
         Beim Suchen der Telefonnummer ist ein Fehler aufgetreten
         Wähltastatur
    @@ -2106,7 +2070,6 @@
         Spaces Feedback
         Dieser Server ist schon in der Liste vorhanden
         Server oder Raumliste kann nicht gefunden werden
    -
         Bei %1$s anfragen
         Zu %1$s weiterleiten
         Space-Adressen
    @@ -2187,7 +2150,7 @@
         Alle im übergeordneten Space haben Zugriff auf den Raum - es ist nicht nötig jeden einzeln einzuladen. Du kannst dies in den Raumeinstellungen jederzeit ändern.
         Andere Spaces oder Räume die du kennst
         Spaces mit diesem Raum und dir als Mitglied
    -    Nur Erwähnungen & Schlüsselwörter
    +    Nur Erwähnungen und Schlüsselwörter
         Auflegen…
         Sprachanruf mit %s
         Videoanruf mit %s
    @@ -2353,7 +2316,6 @@
         Sichere Nachrichtenübertragung.
         Besitze deine Konversationen.
         Um bestehende Kontakte ermitteln zu können, müsst du Kontaktinformationen (E-Mails und Telefonnummern) an Ihren Identitätsserver senden. Wir verschlüsseln deine Daten vor dem Senden, um den Datenschutz zu gewährleisten.
    -
         Deine Kontakte sind privat. Um in deinen Kontakten Benutzer erkennen zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitätsserver zu senden.
         Dieser Server stellt keine Richtlinie bereit.
         Deine Identitätsserver-Richtlinie
    @@ -2430,7 +2392,7 @@
         Hinweis: App wird neugestartet
         diese Frage überspringen
         Noch nicht sicher\? Du kannst %s
    -    Freundschaften und Familie
    +    Freunde und Familie
         In Thread antworten
         Aus einem Thread
         Filter
    @@ -2450,4 +2412,10 @@
             %1$d mehr
         
         Weniger anzeigen
    +    %1$s und %2$s
    +    %1$s, %2$s und andere
    +    
    +        %d Server-ACL geändert
    +        %d Server-ACLs geändert
    +    
     
    \ No newline at end of file
    
    From a71cc5a942ce79ad557c728cb636153c9b999fa5 Mon Sep 17 00:00:00 2001
    From: random 
    Date: Mon, 28 Feb 2022 10:07:30 +0000
    Subject: [PATCH 0038/1430] Translated using Weblate (Italian)
    
    Currently translated at 100.0% (2157 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/it/
    ---
     vector/src/main/res/values-it/strings.xml | 42 ++---------------------
     1 file changed, 2 insertions(+), 40 deletions(-)
    
    diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
    index 849a4e92a4..492d8af526 100644
    --- a/vector/src/main/res/values-it/strings.xml
    +++ b/vector/src/main/res/values-it/strings.xml
    @@ -39,7 +39,6 @@
         Invito nella stanza
         %1$s e %2$s
         Stanza vuota
    -
         Sincronizzazione iniziale:
     \nImportazione account…
         Sincronizzazione iniziale:
    @@ -241,7 +240,6 @@
         Elimina
         Rinomina
         Segnala contenuto
    -
         o
         Invita
         Disconnetti
    @@ -264,7 +262,6 @@
         Mostra solo i contatti Matrix
         Nessun risultato
         Stanze
    -
         Invia i registri
         Invia i registri di crash
         Invia schermata
    @@ -295,11 +292,9 @@
         Questo indirizzo email non sembra corretto
         L\'indirizzo email è già stato impostato.
         Hai dimenticato la password\?
    -
         Questo homeserver vuole assicurarsi che tu non sia un robot
         Va inserito l\'indirizzo email associato al tuo account.
         La verifica del tuo indirizzo email è fallita: assicurati di aver cliccato sul link contenuto nella mail
    -
         Inserisci un URL valido
         JSON malformato
         Non conteneva un JSON valido
    @@ -315,14 +310,10 @@
         Chiamata in corso…
         Ricezione fallita da parte del destinatario.
         Informazione
    -
    -
         ${app_name} deve essere autorizzato ad accedere al microfono e poter così fare chiamate audio.
    -
         ${app_name} deve essere autorizzato ad accedere a fotocamera e microfono per poter fare chiamate video.
     \n
     \nNel prossimo pop-up concedi le autorizzazioni per poter fare la chiamata.
    -
         
         NO
         Continua
    @@ -330,7 +321,6 @@
         Entra
         Rifiuta
         Vai ai non letti
    -
         Esci dalla stanza
         Sei sicuro di voler uscire dalla stanza?
         CHAT DIRETTE
    @@ -357,7 +347,6 @@
         Il certificato è diverso da quello precedentemente contrassegnato sul tuo telefono come \"affidabile\". Questa cosa è MOLTO INSOLITA. Si raccomanda di NON ACCETTARE questo nuovo certificato.
         Il certificato del server è cambiato: quello precedente era stato contrassegnato come affidabile ma quello attuale no. Può darsi che il certificato precedente sia scaduto e sia stato semplicemente sostituito con uno nuovo. Contatta l\'amministratore del server per verificare l\'impronta digitale in uso.
         Contrassegna il certificato come affidabile solo se l\'mpronta digitale comunicata dall\'amministratore del server corrisponde a quella qua sopra.
    -
         Cerca
         Cerca tra i membri della stanza
         Nessun risultato
    @@ -443,7 +432,6 @@
         Queste sono caratteristiche sperimentali che potrebbero dare risultati inattesi. Usali con cautela.
         Imposta come indirizzo principale
         Non usare più come indirizzo principale
    -
         Tema
         Errore di decriptazione
         Nome pubblico
    @@ -455,7 +443,6 @@
         Esporta
         Inserisci la Passphrase
         Conferma la Passphrase
    -
         Importa le chiavi di crittografia E2E della stanza
         Importa le chiavi della stanza
         Importa le chiavi da un file locale
    @@ -467,7 +454,6 @@
         Conferma
         Conferma confrontando la seguente con le impostazioni utente della tua altra sessione:
         Se non corrispondono, la sicurezza delle tue comunicazioni potrebbe essere compromessa.
    -
         Scegli un elenco di stanze
         Nome del server
         Tutte le stanze sull\'Home Server %s
    @@ -509,7 +495,6 @@
         Sicuro di voler fare una chiamata audio\?
         Sicuro di voler fare una videochiamata\?
         Elenco dei membri
    -
         
             %d utente
             %d utenti
    @@ -519,8 +504,6 @@
             %d nuovo messaggio
             %d nuovi messaggi
         
    -
    -
         Tutti i messaggi
         Aggiungi alla schermata iniziale
         Anteprima degli URL
    @@ -530,7 +513,6 @@
             %d messaggio notificato non letto
             %d messaggi notificati non letti
         
    -
         
             %d stanza
             %d stanze
    @@ -608,16 +590,10 @@
         La conversazione continua qui
         Questa stanza contiene una conversazione cominciata altrove
         Clicca qui per vedere i messaggi precedenti
    -
    -
    -
    -
         
             %d selezionato
             %d selezionati
         
    -
    -
         Avvisi di sistema
         contatta l\'amministratore del servizio
         L\'Home Server ha superato uno dei limiti delle risorse, pertanto alcuni utenti non potranno accedere.
    @@ -708,7 +684,6 @@
         ${app_name} non è influenzato dall\'ottimizzazione della batteria.
         Se si lascia un dispositivo scollegato, fermo e con lo schermo spento, dopo un certo tempo questo entra in modalità Doze. Ciò impedisce alle App di accedere alla rete e ritarda le attività, le sincronizzazioni e la ricezione dei normali allarmi.
         Ignora l\'ottimizzazione
    -
         Non è stato trovato nessun APK Google Play Services valido. Le notifiche non funzioneranno correttamente.
         Chiamata video in corso…
         Backup delle chiavi
    @@ -778,7 +753,6 @@
         Salva il codice di recupero
         Condividi
         Salva come file
    -
         Si prega di farne una copia
         Condividi il codice di recupero con…
         Generazione del codice di recupero basato sulla Passphrase. Questo processo può durare alcuni secondi.
    @@ -826,7 +800,6 @@
         Eliminazione Backup…
         Elimina Backup
         Eliminare il Backup delle chiavi crittografiche dall\'Home Server\? Non potrai più usare il codice di recupero per leggere i messaggi cifrati.
    -
         Non perdere mai i messaggi cifrati
         Usa il Backup delle chiavi
         Nuove chiavi per messaggi sicuri
    @@ -840,7 +813,6 @@
         Versione
         Algoritmo
         Firma
    -
         Multimedia
         Compressione predefinita
         Scegli
    @@ -874,8 +846,6 @@
         Ignora
         Verificato!
         Capito
    -
    -
         Richiesta di verifica
         %s vuole verificare la tua sessione
         Errore sconosciuto
    @@ -972,7 +942,6 @@
         Nessuno
         Revoca
         Disconnetti
    -
         Non è stato trovato alcun Home Server seguendo questo URL. Per favore, controllalo
         Modalità sync in background
         Ottimizzato per la batteria
    @@ -983,7 +952,6 @@
     \nCiò avrà un certo impatto sulla quantità di dati e batteria utilizzati. Una notifica sempre accesa comunicherà che ${app_name} è attivo.
         Nessuna sincronizzazione in background
         Quando l\'App è in background non ti verranno notificati i messaggi in arrivo.
    -
         Farsi trovare
         Gestisci le impostazioni per farsi trovare.
         Non stai usando alcun server di identità
    @@ -1053,7 +1021,6 @@
         Questo contenuto è stato segnalato come inappropriato.
     \n
     \nSe non vuoi più vedere i contenuti di questo utente puoi ignorarlo. Ciò nasconderà i suoi messaggi dalla tua vista.
    -
         Integrazioni
         Usa un gestore di integrazioni per gestire bot, bridge, widget e pacchetti di sticker.
     \nI gestori di integrazioni possono ricevere dati di configurazione, modificare widget, mandare inviti alle stanze e modificare permessi a tuo nome.
    @@ -1264,7 +1231,6 @@
         Verifica %s
         %s verificato
         In attesa di %s…
    -
         I messaggi in questa stanza non sono cifrati E2E.
         I messaggi in questa stanza sono cifrati E2E.
     \n
    @@ -1410,7 +1376,6 @@
         Stampala e conservala in un posto sicuro
         Salvala in una penna USB o disco di backup
         Copiala sul Cloud
    -
         Crittografia attiva
         I messaggi in questa stanza sono crittografati E2E. Maggiori info e verifica degli utenti nel loro profilo.
         Crittografia non attiva
    @@ -1813,7 +1778,6 @@
         Nascondi avanzate
         Mostra avanzate
         %1$d di %2$d
    -
         Dai il consenso
         Revoca il mio consenso
         Hai acconsentito ad inviare email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti.
    @@ -1902,8 +1866,6 @@
         Trasferisci
         Connetti
         Prima consulta
    -
    -
         Chiamata attiva (%1$s)
         Si è verificato un errore cercando il numero di telefono
         Tastierino numerico
    @@ -2102,7 +2064,6 @@
         Inserisci il nome di un nuovo server che vuoi esplorare.
         Aggiungi un nuovo server
         Il tuo server
    -
         Spiacenti, si è verificato un errore tentando di entrare: %s
         Indirizzo dello spazio
         Vedi e gestisci gli indirizzi di questo spazio.
    @@ -2303,7 +2264,6 @@
         Domanda o argomento del sondaggio
         Crea sondaggio
         Sondaggio
    -
         Invia email e numeri di telefono a %s
         I tuoi contatti sono privati. Per trovare utenti dai tuoi contatti, ci serve l\'autorizzazione per inviare le informazioni dei contatti al tuo server d\'identità.
         La sessione è stata disconnessa!
    @@ -2448,4 +2408,6 @@
             %d modifica delle ACL del server
             %d modifiche delle ACL del server
         
    +    %1$s e %2$s
    +    %1$s, %2$s e altri
     
    \ No newline at end of file
    
    From 8f1cd11bbbef8c96defac371a4a8a55a57c75699 Mon Sep 17 00:00:00 2001
    From: Suguru Hirahara 
    Date: Wed, 2 Mar 2022 16:21:58 +0000
    Subject: [PATCH 0039/1430] Translated using Weblate (Japanese)
    
    Currently translated at 98.3% (2121 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
    ---
     vector/src/main/res/values-ja/strings.xml | 9 +++++++--
     1 file changed, 7 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
    index fe125881f4..fbd6540dd6 100644
    --- a/vector/src/main/res/values-ja/strings.xml
    +++ b/vector/src/main/res/values-ja/strings.xml
    @@ -1974,7 +1974,7 @@
         国際電話番号は「+」から始まる必要があります
         コードを%1$sに送信しました。以下に入力して認証してください。
         このメールアドレスはどのアカウントにも登録されていません
    -    パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションから鍵をエクスポートしておいてください。
    +    パスワードを変更すると、全てのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションからルームの鍵をエクスポートしておいてください。
         パスワードの再設定を確認するために認証メールを送信します。
         このメールアドレスはどのアカウントにも属していません。
         このアプリではこのホームサーバーにアカウントを作成できません。
    @@ -2322,7 +2322,7 @@
     \n検証は端末に保存され、新しいバージョンのアプリで共有されます。
         %1$s、%2$s他
         %1$sと%2$s
    -    自分と相手を認証してチャットを安全に保ちます
    +    自分と相手を認証して、チャットを安全に保ちましょう
         あなたしか知らないセキュリティーフレーズを入力してください。サーバーで機密情報を保護するために使用します。
         監査結果をエクスポート
         ストレージから機密情報を発見できません
    @@ -2335,4 +2335,9 @@
         アカウントのセキュリティーが破られている可能性があります
         選択したスペースに追加
         最新の${app_name}は他のデバイスでも使用できます:
    +    音声通話が終了しました・%1$s
    +    ビデオ通話が終了しました・%1$s
    +    メールアドレスで招待したり、連絡先を検索したりできます…
    +    鍵のバックアップの機密情報をSSSSに保存しています
    +    あなたしか知らないセキュリティーフレーズを入力してください。サーバーで機密情報を保護するために使用します。
     
    \ No newline at end of file
    
    From 84a27d23fdbd8bf8ba093eb68ff8c6f34a7eb796 Mon Sep 17 00:00:00 2001
    From: Arusekk 
    Date: Wed, 2 Mar 2022 20:59:53 +0000
    Subject: [PATCH 0040/1430] Translated using Weblate (Polish)
    
    Currently translated at 93.8% (2025 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/
    ---
     vector/src/main/res/values-pl/strings.xml | 75 +++++------------------
     1 file changed, 17 insertions(+), 58 deletions(-)
    
    diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
    index 408fc207f5..fe98d6b6a1 100644
    --- a/vector/src/main/res/values-pl/strings.xml
    +++ b/vector/src/main/res/values-pl/strings.xml
    @@ -30,7 +30,6 @@
         Zaproszenie do pokoju
         %1$s i %2$s
         Pusty pokój
    -
         ** Nie można odszyfrować: %s **
         %s wykonał(a) rozmowę wideo.
         %s wykonał(a) połączenie głosowe.
    @@ -46,14 +45,14 @@
         Wstępna synchronizacja:
     \nImportowanie kryptografii
         Wstępna synchronizacja:
    -\nImportowanie pokoi
    +\nImportowanie pokojów
         Wstępna synchronizacja:
     \nImportowanie Twoich konwersacji
    -\nJeśli dołączyłeś(aś) do wielu pokoi, może to zająć dłuższą chwilę
    +\nJeśli dołączyłeś(aś) do wielu pokojów, może to zająć dłuższą chwilę
         Wstępna synchronizacja:
    -\nImportowanie zaproszonych pokoi
    +\nImportowanie zaproszonych pokojów
         Wstępna synchronizacja:
    -\nImportowanie opuszczonych pokoi
    +\nImportowanie opuszczonych pokojów
         Wstępna synchronizacja:
     \nImportowanie grup
         Wstępna synchronizacja:
    @@ -74,7 +73,6 @@
         Usuń
         Zmień nazwę
         Zgłoś treść
    -
         i
         Zaproś
         Wyloguj się
    @@ -97,7 +95,6 @@
         Tylko kontakty Matrixa
         Brak wyników
         Pokoje
    -
         Wyślij dzienniki
         Wyślij dzienniki awarii
         Wyślij zrzut ekranu
    @@ -141,7 +138,6 @@
         Przychodzące połączenie głosowe
         W trakcie połączenia…
         Informacja
    -
         ${app_name} wymaga uprawnienia, aby przeprowadzić połączenie audio.
         TAK
         NIE
    @@ -169,7 +165,6 @@
         Ignoruj
         Odcisk palca (%s):
         Nie można zweryfikować tożsamości serwera.
    -
         Szukaj
         Filtruj członków pokoju
         Brak wyników
    @@ -209,7 +204,6 @@
         Zaaktualizuj nazwę publiczną
         Ostatnio widziany(-a)
         %1$s @ %2$s
    -
         Uwierzytelnianie
         Zalogowany jako
         Interfejs użytkownika
    @@ -240,7 +234,6 @@
         Laboratorium
         Znajdują się tu eksperymentalne funkcje, których należy używać z ostrożnością.
         Ustaw jako główny adres
    -
         Motyw
         Nazwa publiczna
         ID sesji
    @@ -283,8 +276,6 @@
         Czy jesteś pewien, że chcesz rozpocząć wideorozmowę?
         Zrób zdjęcie
         Nagraj film
    -
    -
         Uszkodzony JSON
         
             %d zmiana członkostwa
    @@ -293,15 +284,10 @@
             
         
         Rozmówca nie połączył się.
    -
    -
         ${app_name} wymaga dostępu do kamery i mikrofonu, aby przeprowadzać rozmowy wideo.
     \n
     \nPrzyznaj dostęp w następnym oknie.
    -
         Lista uczestników
    -
    -
         
             1 członek
             kilku członków
    @@ -323,8 +309,6 @@
         Certyfikat różni się od tego zaufanego w twoim telefonie. Serwer mógł odnowić certyfikat. Skontaktuj się z administratorem serwera w celu weryfikacji odcisku palca.
         Certyfikat zmienił stan z zaufanego na niezaufany. jest to NIEZWYKLE RZADKIE. Rekomendowane jest NIE AKCEPTOWANIE nowego certyfikatu.
         Akceptuj certyfikat tylko wtedy gdy administrator opublikował odcisk palca pasujący do tego powyżej.
    -
    -
         Wszystkie wiadomości
         Dodaj do ekranu domowego
         Pokaż informacje o aplikacji w ustawieniach systemu.
    @@ -363,7 +347,6 @@
         Wysyłaj dane analityczne
         ${app_name} zbiera anonimowe informacje które pozwolą ulepszyć aplikację.
         Eksportuj klucze E2E pokoju
    -
         Importuj klucze E2E pokoju
         Importuj klucze pokoju
         Importuj klucze z lokalnego pliku
    @@ -372,14 +355,13 @@
         Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesji w tym pokoju z tej sesji.
         Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej:
         Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany.
    -
         Wyślij naklejkę
         Niestety, nie znaleziono zewnętrznej aplikacji, która ukończy to działanie.
         
             1 pokój
             %d pokoje
    -        %d pokoi
    -        
    +        %d pokojów
    +        %d pokojów
         
         
             1 aktywny widżet
    @@ -399,7 +381,6 @@
             %d nieprzeczytanych wiadomości powiadomienia
             
         
    -
         %1$s w %2$s
         Czy na pewno chcesz usunąć widżet z tego pokoju?
         Nie można utworzyć widżetu.
    @@ -436,16 +417,10 @@
         Błąd
         Opuszcza pokój
         Włącza/Wyłącza markdown
    -
    -
         Kliknij tutaj, aby zobaczyć starsze wiadomości
         Przepraszamy, wystąpił błąd
         Alerty systemowe
         Jeżeli to możliwe, proszę napisz opis w języku angielskim.
    -
    -
    -
    -
         
             1 zaznaczone
             %d zaznaczone
    @@ -603,7 +578,6 @@
         Jeżeli nie pamiętasz swoich danych odzystkiwania, możesz %s.
         Zgubiłeś (-łaś) swój klucz odzyskiwania\? Możesz ustawić nowy w ustawieniach.
         Kopia zapasowa posiada poprawną sygnaturę z niezweryfikowanej sesji %s
    -
         Jesteś na bieżąco!
         Unieważnij
         Rozłącz
    @@ -652,7 +626,6 @@
         Niepowodzenie przy pobieraniu wersji kluczy (%s).
         Usuwanie kopii zapasowej…
         Sprawdzanie stanu kopii zapasowej
    -
         Reakcje
         Brak sieci. Sprawdź swoje połączenie z Internetem.
         Proszę czekać…
    @@ -682,7 +655,6 @@
         IGNORUJ UŻYTKOWNIKA
         Treść zgłoszona
         Zgłoszone jako spam
    -
         Proszę napisz swoją sugestię poniżej.
         Opisz swoją sugestię tutaj
         Utwórz nową rozmowę bezpośrednią
    @@ -712,7 +684,6 @@
         Opuść pokój
         Włącz szyfrowanie end-to-end…
         Brak
    -
         Nie udało się połączyć z serwerem o podanym adresie URL, upewnij się, że wpisano go poprawnie
         Niektóre rodzaje wiadomości będą ciche (wygenerują powiadomienie bez dźwięku).
         Weryfikacja Usług Google
    @@ -761,9 +732,8 @@
     \nWpłynie to na użycie baterii i sieci, na panelu powiadomień pozostanie wyświetlone stałe powiadomiene o nasłuchiwaniu zdarzeń.
         Brak synchronizacji w tle
         Nie będziesz otrzymywać powiadomień o przychodzących wiadomościach gdy aplikacja będzie działać w tle.
    -
         Użyj menedżera integracji aby zarządzać botami, mostami, widżetami i pakietami naklejek.
    -\nMenedżerzy integracji odbierają dane konfiguracji, modyfikują widżety, wysyłają zaproszenia do pokoi i ustawiają poziomy uprawnień na Twe żądanie.
    +\nMenedżerzy integracji odbierają dane konfiguracji, modyfikują widżety, wysyłają zaproszenia do pokojów i ustawiają poziomy uprawnień w Twoim imieniu.
         Pokaż podgląd linków wewnątrz czatu jeśli twój serwer wspiera tę funkcję.
         Formatuj wiadomości używając składni Markdown zanim zostaną wysłane. Pozwala to na zaawansowane formatowanie takie jak używanie asterysków do wyświetlania tekstu w kursywie.
         Nie wpływa to na zaproszenia, wyrzucenia oraz bany.
    @@ -771,7 +741,6 @@
         Przycisk enter na klawiaturze programowej wyśle wiadomość zamiast wprowadzania łamanania linii
         Znajdź
         Zarządzaj ustawieniami wyszukiwania.
    -
         Media
         Domyślne źródło mediów
         Odzyskiwanie zaszyforwanych wiadomości
    @@ -832,7 +801,6 @@
         Zrobiłem kopię
         Zapisz Klucz Odzyskiwania
         Zapisz jako plik
    -
         Kopia zapasowa istnieje już na Twoim serwerze domowym
         Wygląda na to, iż kopia zapasowa kluczy została skonfigurowana za pomocą innej sesji. Czy chcesz zastąpić ją tą, którą tworzysz\?
         Generowanie Klucza Odzyskiwania używając hasła, proces może zająć kilka sekund.
    @@ -861,7 +829,6 @@
             Kopiowanie %d kluczy…
             
         
    -
         Niektóre powiadomienia są wyłączone w osobistej konfiguracji.
         Usługi Google Play są aktualne.
         Rozumiem
    @@ -930,7 +897,6 @@
         Zawartość została zgłoszona jako niewłaściwa.
     \n
     \nJeżeli nie chcesz widzieć treści od tego użytkownika, możesz go zablokować aby ukryć jego wiadomości.
    -
         Opuść pokój
         %1$s nie dokona(-ła) zmian
         Wysyła wiadomość jako spoiler
    @@ -1109,7 +1075,6 @@
         Zweryfikuj %s
         Zweryfikowano %s
         Oczekiwanie na %s…
    -
         Wiadomości w tym pokoju są zaszyfrowane end-to-end.
     \n
     \nTwoje wiadomości są zabezpieczone zamkami i jedynie Ty oraz Twój odbiorca posiadacie klucze, aby je odblokować.
    @@ -1222,7 +1187,7 @@
         Szyfrowanie miniatury…
         Nie możesz czegoś odnaleźć\?
         Utwórz nowy pokój
    -    Otwórz katalog pokoi
    +    Otwórz katalog pokojów
         Nazwa lub ID (#przykład:matrix.org)
         Serwer toższamości
         Odłącz od serwera tożsamości
    @@ -1345,7 +1310,6 @@
         Jeżeli teraz przerwiesz, możesz utracić zaszyfrowane wiadomości oraz dane jeżeli utracisz dostęp do zalogowanych sesji. 
     \n
     \nMożesz także ustawić Secure Backup i zarządzać swoimi kluczami w Ustawieniach.
    -
         Wydrukuj go i schowaj w bezpiecznym miejscu
         Twój %2$s i %1$s są teraz ustawione.
     \n
    @@ -1457,7 +1421,6 @@
         Dodaj obraz z
         Niepoprawny kod weryfikacyjny.
         Kod
    -
         Udziel zgody
         Wycofaj moją zgodę
         Udzieliłeś zgody na wysłanie adresów e-mail oraz numerów telefonów do tego serwera tożsamości w celu odkrycia innych użytkowników z Twoich kontaktów.
    @@ -1508,7 +1471,7 @@
             %d zablokowanych użytkowników
             %d zablokowanych użytkowników
         
    -    Opublikować ten pokój dla wszystkich w katalogu pokoi %1$s\?
    +    Opublikować ten pokój dla wszystkich w katalogu pokojów %1$s\?
         Cofnij publikację tego adresu
         Opublikuj ten adres
         Dodaj adres lokalny
    @@ -1526,7 +1489,7 @@
         To jest główny adres
         Opublikowane adresy mogą zostać wykorzystane przez kogokolwiek na dowolnym serwerze do dołączenia do Twojego pokoju. Żeby opublikować adres musi być on najpierw ustawiony jako adres lokalny.
         Opublikowane adresy
    -    Obejrzyj i zarządzaj adresami tego pokoju oraz jego widocznością w katalogu pokoi.
    +    Obejrzyj i zarządzaj adresami tego pokoju oraz jego widocznością w katalogu pokojów.
         Adres pokoju
         Dostęp do pokoju
         Zmiany dotyczące tego kto może czytać historię zostaną zastosowane wyłącznie do przyszłych wiadomości w tym pokoju. Widoczności już istniejącej historii pozostanie niezmieniona.
    @@ -1557,7 +1520,7 @@
         Anuluj zaproszenie
         Zaprzestanie ignorowania użytkownika spowoduje ponowne wyświetlanie wszystkich jego wiadomości.
         Przestań ignorować użytkownika
    -    Zignorowanie tego użytkownika usunie wszystkie jego wiadomości z pokoi, które współdzielicie.
    +    Zignorowanie tego użytkownika usunie wszystkie jego wiadomości z pokojów, które współdzielicie.
     \n
     \nOperacja ta może zostać cofnięta w dowolnej chwili poprzez ustawienia ogólne.
         Ignoruj użytkownika
    @@ -1895,7 +1858,7 @@
         Adres URL serwera domowego
         Wyślij historię żądań udostępnienia klucza
         Przestrzenie
    -    Katalog pokoi
    +    Katalog pokojów
         Sugerowane pokoje
         Nowa wartość
         Przełącz
    @@ -1984,7 +1947,7 @@
         Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia.
         Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia. 
     \nNie masz uprawnień aby dodawać pokoje.
    -    W tej Przestrzeni nie ma żadnych pokoi
    +    W tej Przestrzeni nie ma żadnych pokojów
         Proszę skontaktować się z administratorem Twojego serwera domowego aby uzyskać więcej informacji
         Wygląda na to, że Twój serwer domowy jeszcze nie obsługuje Przestrzeni
         Lubisz eksperymentować\?
    @@ -2006,7 +1969,7 @@
         Dodaj istniejące pokoje i przestrzenie
         Wybierz aby opuścić
         Opuść wybrane pokoje i przestrzenie…
    -    Nie opuszczaj żadnych pokoi i przestrzeni
    +    Nie opuszczaj żadnych pokojów i przestrzeni
         Opuść wszystkie pokoje i przestrzenie
         Jesteś jedynym administratorem tej przestrzeni. Opuszczenie jej oznacza brak kontroli nad nią.
         Nie będziesz w stanie ponownie dołączyć, do momentu kiedy nie zostaniesz ponownie zaproszony.
    @@ -2051,7 +2014,7 @@
         Publiczna
         Prywatna przestrzeń dla Ciebie i Twoich znajomych
         Ja i moi znajomi
    -    Prywatna przestrzeń do organizacji Twoich pokoi
    +    Prywatna przestrzeń do organizacji Twoich pokojów
         Tylko ja
         Upewnij się, że odpowiednie osoby mają dostęp do %s. Możesz zmienić to później.
         Z kim pracujesz\?
    @@ -2132,7 +2095,7 @@
         Opuścić obecną konferencję i przejść do innej\?
         Wystąpił błąd podczas próby dołączenia do konferencji
         Ten serwer znajduje się już na liście
    -    Nie można odnaleźć tego serwera lub jego listy pokoi
    +    Nie można odnaleźć tego serwera lub jego listy pokojów
         Wprowadź nazwę nowego serwera, który chcesz odkrywać.
         Dodaj nowy serwer
         Twój serwer
    @@ -2149,7 +2112,7 @@
         wyproszenie użytkownika zaskutkuje usunięciem go z tej przestrzeni.
     \n
     \nAby uniemożliwić mu ponowne dołączenie, należy go zbanować.
    -    Pokaż wszystkie pokoje w katalogu pokoi (również te zawierające treści dla dorosłych).
    +    Pokaż wszystkie pokoje w katalogu pokojów (również te zawierające treści dla dorosłych).
         Pokaż pokoje z treścią dla dorosłych
         %1$s zmienił(a) alternatywne adresy dla tego pokoju.
         Dodano %1$s i usunięto %2$s jako adresy tego pokoju.
    @@ -2190,7 +2153,6 @@
         Ustawienia pokoju
         Ankieta
         Plik jest zbyt duży, aby go przesłać.
    -
         Wyślij maile i numery telefonów do %s
         Twoje kontakty są prywatne. Aby odnaleźć użytkowników z Twoich kontaktów, potrzebujemy zgody do wysłania informacji o nich na Twój serwer tożsamości.
         Bezpieczna kopia
    @@ -2273,7 +2235,6 @@
         Aktualizuj pokój prywatny
         Aktualizuj pokój publiczny
         Dołącz do pokoju zastępczego
    -
         Dodaj kilka szczegółów, aby ułatwić ludziom identyfikację. Możesz je zmienić w dowolnym momencie.
         Dodaj kilka szczegółów, aby się wyróżnić. Możesz je zmienić w dowolnym momencie.
         Twoja przestrzeń prywatna
    @@ -2336,7 +2297,6 @@
             %1$d aktywnych połączeń ·
             %1$d aktywnych połączeń ·
         
    -
         Zgadzasz się na wysłanie tych informacji\?
         Ustawienia systemu
         Wersje
    @@ -2371,7 +2331,6 @@
         Niedostępny
         Dostępny
         Pokaż znaczniki odczytania
    -
         Odkrywanie (%s)
         Dokończ konfigurację odkrywania.
         Usuń wszystkie nieudane wiadomości
    
    From abfd9b8fdcc4ce95aaf196d2d497c46c6586aa20 Mon Sep 17 00:00:00 2001
    From: Jozef Gaal 
    Date: Tue, 1 Mar 2022 23:41:47 +0000
    Subject: [PATCH 0041/1430] Translated using Weblate (Slovak)
    
    Currently translated at 98.9% (2134 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
    ---
     vector/src/main/res/values-sk/strings.xml | 16 +++++++++-------
     1 file changed, 9 insertions(+), 7 deletions(-)
    
    diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
    index 18af56a598..6ec0827757 100644
    --- a/vector/src/main/res/values-sk/strings.xml
    +++ b/vector/src/main/res/values-sk/strings.xml
    @@ -658,7 +658,7 @@
         Ignorovať
         Ste si istí, že sa chcete odhlásiť\?
         Označiť ako prečítané
    -    Prihlásiť sa použitím jediného prihlásenia
    +    Prihlásiť sa použitím jednotného prihlásenia SSO
         Pokročilé nastavenia oznámení
         Dôležitosť oznámení pre udalosti
         Vlastné nastavenia.
    @@ -1650,8 +1650,8 @@
         Vlastné (%1$d) v %2$s
         Predvolené v %1$s
         Overiť porovnaním emotikonov
    -    Namiesto toho overte porovnaním emoji
    -    Ak nie ste osobne, porovnajte namiesto toho emoji
    +    Namiesto toho overte porovnaním emotikonov
    +    Ak nie ste osobne, porovnajte namiesto toho emotikony
         Skenovať pomocou tohto zariadenia
         Naskenujte kód pomocou iného zariadenia alebo prepnite a skenujte pomocou tohto zariadenia
         Naskenujte kód pomocou zariadenia druhého používateľa, aby ste sa navzájom bezpečne overili
    @@ -2017,7 +2017,7 @@
         Vyberte si prosím heslo.
         Vyberte si prosím používateľské meno.
         Nepodarilo sa nastaviť krížové podpisovanie
    -    Interaktívne overte pomocou emoji
    +    Interaktívne overte pomocou emotikonov
         Manuálne overte pomocou textu
         Overte nové prihlásenie s prístupom k vášmu účtu: %1$s
         Overte všetky vaše relácie, aby ste si boli istý, že sú vaše správy a účet bezpečné
    @@ -2115,7 +2115,7 @@
         Krížové podpisovanie nie je povolené
         Vaša nová relácia je teraz overená. Má prístup k vašim šifrovaným správam a ostatní používatelia ju uvidia ako dôveryhodnú.
         Porovnajte kód s kódom zobrazeným na obrazovke druhého používateľa.
    -    Porovnajte jedinečné emoji a uistite sa, že sú zobrazené v rovnakom poradí.
    +    Porovnajte jedinečné emotikony a uistite sa, že sú zobrazené v rovnakom poradí.
         Aby ste si boli istý, urobte to osobne alebo použite iný dôveryhodný spôsob komunikácie.
         Ak chcete byť v bezpečí, overte %s pomocou jednorazového kódu.
         Po zapnutí šifrovania pre miestnosť ho už nemožno vypnúť. Správy odoslané v zašifrovanej miestnosti nemôže vidieť server, iba účastníci miestnosti. Zapnutie šifrovania môže zabrániť správnemu fungovaniu mnohých botov a premostení.
    @@ -2324,8 +2324,8 @@
         Kto má prístup\?
         Zmeny týkajúce sa toho, kto môže čítať históriu, sa budú vzťahovať len na budúce správy v tejto miestnosti. Viditeľnosť existujúcej histórie zostane nezmenená.
         Tento server neposkytuje žiadne zásady a pravidlá.
    -    Pridať tlačidlo do editora správ na otvorenie klávesnice emodži
    -    Zobraziť klávesnicu emodži
    +    Pridať tlačidlo do editora správ na otvorenie klávesnice emotikonov
    +    Zobraziť klávesnicu emotikonov
         Použite príkaz /confetti alebo pošlite správu obsahujúcu ❄️ alebo 🎉
         Zobraziť efekty konverzácie
         Relácia bola odhlásená!
    @@ -2437,4 +2437,6 @@
         
         %1$s, %2$s a ďalší
         %1$s a %2$s
    +    Pokračovať pomocou jednotného prihlásenia SSO
    +    jednotné prihlásenie SSO
     
    \ No newline at end of file
    
    From a251545e904cf0f0e3e43ba727e3c77f5adb8799 Mon Sep 17 00:00:00 2001
    From: Besnik Bleta 
    Date: Wed, 2 Mar 2022 09:39:16 +0000
    Subject: [PATCH 0042/1430] Translated using Weblate (Albanian)
    
    Currently translated at 99.4% (2145 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/
    ---
     vector/src/main/res/values-sq/strings.xml | 57 ++++++-----------------
     1 file changed, 15 insertions(+), 42 deletions(-)
    
    diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
    index c363b8e4e9..48fc274eb7 100644
    --- a/vector/src/main/res/values-sq/strings.xml
    +++ b/vector/src/main/res/values-sq/strings.xml
    @@ -39,7 +39,6 @@
         %1$s hoqi emrin e tij në ekran (%2$s)
         %1$s hoqi temën e dhomës
         %1$s dërgoi një ftesë për %2$s që të marrë pjesë në dhomë
    -
         %s e përmirësoi këtë dhomë.
         Njëkohësimi fillestar:
     \nPo importohet llogaria…
    @@ -315,10 +314,6 @@
         Hidheni tej
         Anëtarë liste
         Hidhu te të palexuarit
    -
    -
    -
    -
         Dilni nga dhoma
         Jeni i sigurt se doni të dilni nga dhoma?
         FJALOSJE TË DREJTPËRDREJTA
    @@ -501,8 +496,6 @@
         Kreu
         Dhoma
         I ftuar
    -
    -
         Jeni përzënë prej %1$s nga %2$s
         Jeni dëbuar prej %1$s nga %2$s
         Arsye: %1$s
    @@ -520,7 +513,6 @@
         %1$s: %2$s
         %d+
         Po përgjohet për akte
    -
         Të parapëlqyer
         Ju lutemi, përshkruajeni të metën. Ç’po bënit? Ç’prisnit të ndodhte? Ç’ndodhi në fakt?
         Duket se po përplasni telefonin nga inati. Do të donit të hapej skena për njoftim të metash?
    @@ -528,17 +520,12 @@
         Dështoi dërgimi i njoftimit të të metës (%s)
         Dilni
         Dërgoni mesazh zanor
    -
         Verifikimi i adresës email dështoi: sigurohuni se keni klikuar lidhjen te email-i
    -
         Ju lutemi, niseni ${app_name}-in në një tjetër pajisje që mund të shfshehtëzojë mesazhin, që kështu të mund të dërgojë kyçet te ky sesion.
    -
         %1$s & %2$s & të tjerë po shtypin…
         version olm
         Fikso dhomat me njoftime të humbura
         Vetëm anëtarët (që nga çasti i përzgjedhjes së kësaj mundësie)
    -
    -
         Përdor kamerë të brendshme
         Shtuat një sesion të ri \'%s\', i cili po kërkon kyçe fshehtëzimi.
         Sesioni juaj i paverifikuar \'%s\' po kërkon kyçe fshehtëzimi.
    @@ -551,7 +538,6 @@
         Ju lutemi, %s për ta zmadhuar këtë kufi.
         Ju lutemi, %s që të vazhdoni përdorimin e këtij shërbimi.
         ose
    -
         Në qoftë e mundur, ju lutemi, përshkrimin shkruajeni në anglisht.
         Hëpërhë, s’keni të aktivizuar ndonjë pako ngjitësash.
     \n
    @@ -565,16 +551,11 @@
         
         Po bëhet lidhja e thirrjes…
         Ana e largët dështoi të përgjigjet.
    -
    -
         Për të kryer thirrje audio, ${app_name}-i lyp leje të përdorë mikrofonin tuaj.
    -
         Për të kryer thirrje video, ${app_name}-i lyp leje të përdorë kamerën dhe mikrofonin tuaj.
     \n
     \nJu lutemi, lejoni përdorimin, që nga flluskat pasuese, që të jetë në gjendje të bëjë thirrjen.
    -
         JO
    -
         
             %d anëtar
             %d anëtarë
    @@ -585,14 +566,9 @@
             %d mesazh i ri
             %d mesazhe të rinj
         
    -
    -
    -
    -
         Ju lutemi, kontrolloni email-in tuaj dhe klikoni mbi lidhjen që përmban. Pasi të jetë bërë kjo, klikoni që të vazhdohet.
         Këto janë veçori eksperimentale që mund të ngecin në rrugë të papritura. Përdorini me kujdes.
         Ju lutemi, krijoni një frazëkalim për fshehtëzimin e kyçeve të eksportuar. Që të jeni në gjendje t’i importoni kyçet, do t’ju duhet të jepni të njëjtin frazëkalim.
    -
         Mos dërgo kurrë prej këtij sesioni mesazhe të fshehtëzuar te sesione të paverifikuar.
         Ripohojeni duke krahasuar sa vijon me Rregullimet e Përdoruesit te sesioni juaj tjetër:
         Nëse s’përputhen, siguria e komunikimeve tuaja mund të jetë komprometuar.
    @@ -639,7 +615,6 @@
             %d mesazh i njoftuar i palexuar
             %d mesazhe të njoftuar të palexuar
         
    -
         Dërgoni një ngjitës
         Dërgoni një ngjitës
         Thirrje
    @@ -700,7 +675,6 @@
         ${app_name}-i nuk preket nga Optimizime Baterie.
         Nëse një përdorues e lë një pajisje jo në prizë dhe të palëvizshme për një periudhë, me ekranin të fikur, pajisja kalon nën mënyrën Dremitje. Kjo u parandalon aplikimeve të hyjnë në rrjet dhe shtyn për më vonë punët e tyre, njëkohësimet dhe alarmet standarde.
         Shpërfille Optimizimin
    -
         S’u gjet APK për Google Play Services. Njoftimet mund të mos punojnë saktë.
         Thirrje Video Në Kryerje e Sipër…
         Kopjeruajtje Kyçesh
    @@ -737,7 +711,6 @@
         U bë
         Ruani Kyç Rimarrjesh
         Ruaje si Skedë
    -
         Ju lutemi, bëni një kopje
         Jepjani kyçin e rimarrjeve…
         Po prodhohet Kyç Rimarrjesh duke përdorur frazëkalim, ky proces mund të hajë disa sekonda.
    @@ -831,8 +804,6 @@
         Po shkarkohen kyçe…
         Po importohen kyçe…
         Që të përdorni Kopjeruajtje Kyçesh në këtë sesion, rikthejeni tani përmes frazëkalimit tuaj ose kyçi rimarrjesh.
    -
    -
         Media
         Ngjeshje parazgjedhje
         Zgjidhni
    @@ -869,8 +840,6 @@
         Shpërfille
         U verifikua!
         E mora vesh
    -
    -
         Kërkesë Verifikimi
         %s dëshiron të verifikojë sesionin tuaj
         Gabim i Panjohur
    @@ -965,7 +934,6 @@
         Asnjë
         Shfuqizoje
         Shkëputu
    -
         S’kapet dot shërbyes Home te kjo URL, ju lutemi, kontrollojeni
         Mënyrë Njëkohësimi Në Prapaskenë
         E optimizuar për baterinë
    @@ -976,7 +944,6 @@
     \nKjo do të ketë ndikim mbi përdorimin e baterisë dhe të transmetimit, do të shfaqet një njoftim i pandërprerë që pohon se ${app_name}-i po përgjon për akte.
         Pa njëkohësim në prapraskenë
         S’do të njoftoheni për mesazhe ardhës, kur aplikacioni gjendet në prapaskenë.
    -
         Administroni rregullimet tuaja për zbulime.
         S’po përdorni ndonjë shërbyes identitetesh
         Duket se po rrekeni të lidheni me një tjetër shërbyes Home. Doni të bëhet dalja\?
    @@ -1045,7 +1012,6 @@
         Kjo lëndë është raportuar si e papërshtatshme. 
     \n 
     \nNëse s’doni të shihni më lëndë nga ky përdorues, mund ta shpërfillni, që të fshihen mesazhet e tij.
    -
         Integrime
         Përdorni një përgjegjës integrimesh që të administroni robotë, ura, widget-e dhe paketa ngjitësish.
     \nPërgjegjësit e integrimeve marrin të dhëna formësimi dhe mund të ndryshojnë widget-e, të dërgojnë ftesa për në dhoma dhe të caktojnë shkallë pushteti në emrin tuaj.
    @@ -1324,7 +1290,6 @@
         %s u pranua
         Skanojeni kodin me pajisjen e përdoruesit tjetër, për të verifikuar në mënyrë të sigurt njëri-tjetrin
         Nëse s’jeni vetë atje, krahasoni emoji-n
    -
         ${app_name} nuk trajton akte të llojit \'%1$s\'
         ${app_name} ndeshi një problem kur vizatohej lëndë e aktit me ID \'%1$s\'
         Ky sesion s’është në gjendje të ndajë këtë verifikim me sesionet tuaj të tjerë.
    @@ -1407,7 +1372,6 @@
         Shtypeni dhe ruajeni diku në një vend të parrezik
         Ruajeni në një diskth USB ose pajisje kopjeruajtjesh
         Kopjojeni te depozita juaj personale në re
    -
         Fshehtëzimi u aktivizua
         Mesazhet në këtë dhomë janë të fshehtëzuara skaj-më-skaj. Mësoni më tepër & verifikoni përdorues te profilet e tyre.
         Fshehtëzim jo i aktivizuar
    @@ -1811,7 +1775,6 @@
         Fshihi të mëtejshmet
         Shfaq të mëtejshme
         %1$d nga %2$d
    -
         Jepe pranimin
         Shfuqizoje pranimin tim
         Keni dhënë pranimin tuaj për të dërguar email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj.
    @@ -1863,8 +1826,6 @@
         Shpërngule
         Lidhe
         Konsultohu së pari
    -
    -
         Thirrje aktive (%1$s)
         Pati një gabim gjatë kërkimit të numrit të telefonit
         Pjesa e numrave
    @@ -2028,7 +1989,7 @@
         Unë dhe anëtarët e ekipit tim
         Një hapësirë private për të sistemuar dhomat tuaja
         Unë vetëm
    -    Siguroni që personat e duhur të kenë hyrje te %s. Këtë mund të ndryshoni më vonë.
    +    Siguroni që personat e duhur të kenë hyrje te %s.
         Me cilët po punoni\?
         Që të hyni në një hapësirë ekzistuese, ju duhet një ftesë.
         Këtë mund ta ndryshoni më vonë
    @@ -2102,7 +2063,6 @@
         Jepni emrin e e një shërbyesi të ri që doni të eksploroni.
         Shtoni shërbyes të ri
         Shërbyesi juaj
    -
         Na ndjeni, ndodhi një gabim teksa provohej të hyhej: %s
         Adresë hapësire
         Adresa hapësire
    @@ -2302,7 +2262,6 @@
         Krijoni Pyetësor
         A pranoni të dërgohen këto hollësi\?
         Për të zbuluar kontakte ekzistuese, duhet të dërgoni hollësi kontakti (email-e dhe numra telefonash) te shërbyesi juaj i identiteteve. Para dërgimit, i fshehtëzojmë të dhënat tuaja, për privatësi.
    -
         Dërgo email-e dhe numra telefonash te %s
         Kontaktet tuaja janë private. Për të zbuluar përdorues prej kontakteve tuaja, na duhet leja juaj për të dërguar hollësi kontakti te shërbyesi juaj i identiteteve.
         Është bërë dalja nga sesioni!
    @@ -2435,4 +2394,18 @@
         Kopjoje lidhjen te rrjedha
         Shiheni në dhomë
         Shihni Rrjedha
    +    %1$s dhe %2$s
    +    %1$s, %2$s dhe të tjerë
    +    
    +        %1$d më tepër
    +        %1$d më tepër
    +    
    +    Njofto krejt dhomën
    +    Përdorues
    +    
    +        %d ndryshim ACL-sh shërbyesi
    +        %d ndryshimë ACL-sh shërbyesi
    +    
    +    Njoftim dhome
    +    Shfaq më pak
     
    \ No newline at end of file
    
    From 0b6af0f2b290fee831ccf23316476b532b98de17 Mon Sep 17 00:00:00 2001
    From: Jeff Huang 
    Date: Tue, 1 Mar 2022 02:27:26 +0000
    Subject: [PATCH 0043/1430] Translated using Weblate (Chinese (Traditional))
    
    Currently translated at 100.0% (2157 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/
    ---
     vector/src/main/res/values-zh-rTW/strings.xml | 43 +------------------
     1 file changed, 2 insertions(+), 41 deletions(-)
    
    diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
    index 060b739305..564a4ccf8a 100644
    --- a/vector/src/main/res/values-zh-rTW/strings.xml
    +++ b/vector/src/main/res/values-zh-rTW/strings.xml
    @@ -39,7 +39,6 @@
         聊天室邀請
         %1$s 和 %2$s
         空聊天室
    -
         初始化同步:
     \n正在匯入帳號……
         初始化同步:
    @@ -243,7 +242,6 @@
         刪除
         重新命名
         回報內容
    -
         
         邀請
         登出
    @@ -266,7 +264,6 @@
         僅 Matrix 聯絡人
         沒有結果
         聊天室
    -
         社群
         傳送記錄
         傳送當機紀錄
    @@ -300,11 +297,9 @@
         這看起不像是有效的電子郵件地址
         此電子郵件位址已經被定義。
         忘記密碼?
    -
         這個家伺服器想要確定您不是機器人
         必須輸入和您帳號關聯的電子郵件地址。
         電子郵件地址驗證失敗: 請確保您已點擊郵件中的連結
    -
         請輸入有效的網址
         異常的 JSON
         沒有包含有效的 JSON
    @@ -321,14 +316,10 @@
         通話進行中……
         遠端未能接聽。
         資訊
    -
    -
         ${app_name} 需要權限來存取麥克風,來撥打語音通話。
    -
         ${app_name} 需要權限來存取相機及麥克風來撥打視訊通話。
     \n
     \n為了可以正常使用通話功能,請在下個彈跳視窗中允許存取。
    -
         
         
         繼續
    @@ -337,8 +328,6 @@
         拒絕
         列出成員
         跳到未讀
    -
    -
         
             %d 個成員
         
    @@ -385,12 +374,9 @@
         請在另一個可以解密訊息的裝置上啟動 ${app_name},以便它將金鑰發送到此工作階段。
         憑證已從以前受信任的更改為不受信任的憑證。伺服器可能已續訂其憑證。請與伺服器管理員聯繫以尋找所需的指紋。
         僅當伺服器管理員發佈的指紋與上面的指紋匹配時才接受此憑證。
    -
         搜尋
         過濾聊天室成員
         沒有結果
    -
    -
         所有訊息
         新增到主畫面
         個人檔案圖片
    @@ -449,7 +435,6 @@
         更新公開名稱
         上次上線
         %1$s @ %2$s
    -
         授權
         登入爲
         家伺服器
    @@ -488,7 +473,6 @@
         這些是可能以非預期的方式壞掉的實驗性功能。請小心服用。
         設定為主要地址
         取消設定為主要地址
    -
         主題
         解密錯誤
         公開名稱
    @@ -500,7 +484,6 @@
         匯出
         輸入通關密語
         確認通關密語
    -
         匯入聊天室端到端加密密鑰
         匯入聊天室金鑰
         從本機檔案匯入金鑰
    @@ -512,7 +495,6 @@
         驗證
         透過將以下內容與您的其他工作階段中的使用者設定來確認:
         如果不符合的話,您的通訊安全可能正受到威脅。
    -
         選擇一個聊天室目錄
         伺服器名稱
         在 %s 伺服器上的所有聊天室
    @@ -520,7 +502,6 @@
         
             %d 條未讀的已通知訊息
         
    -
         
             %d 個聊天室
         
    @@ -594,15 +575,9 @@
         對話在此繼續
         這個聊天示是其他對話的延續
         點選這裡以檢視更舊的訊息
    -
    -
    -
    -
         
             %d 已選取
         
    -
    -
         系統警告
         聯絡您的服務管理員
         此家伺服器已經超過其中一項資源限制,所以有一些使用者將會無法登入
    @@ -694,7 +669,6 @@
         ${app_name} 不會被電池最佳化影響。
         如果使用者不為裝置充電,並讓其靜置一段時間,且將螢幕關閉,裝置將會進入 Doze 模式。這可能會導致應用程式無法存取網路,並延遲它們的工作、同步與標準警報。
         忽略最佳化
    -
         找不到有效的 Google Play 服務 APK。通知可能無法正常運作。
         視訊通話進行中……
         金鑰備份
    @@ -731,7 +705,6 @@
         完成
         儲存復原金鑰
         儲存為檔案
    -
         請複製
         分享復原金鑰與……
         正在使用通關密語生成復原金鑰,這個過程可能需要數秒鐘。
    @@ -814,7 +787,6 @@
         演算法
         簽章
         要在此工作階段上使用金鑰備份,現在就使用您的通關密語或復原金鑰復原。
    -
         正在計算復原金鑰……
         正在下載金鑰……
         正在匯出金鑰……
    @@ -823,7 +795,6 @@
         使用 Enter 傳送訊息
         軟體鍵盤的 Enter 按鈕將會傳送訊息而非換行
         密碼無效
    -
         媒體
         預設壓縮
         選擇
    @@ -858,8 +829,6 @@
         忽略
         已驗證!
         知道了
    -
    -
         驗證請求
         %s 想要驗證您的工作階段
         未知錯誤
    @@ -956,7 +925,6 @@
         
         撤銷
         斷線
    -
         無法在此 URL 找到家伺服器,請檢查
         背景同步模式
         為電池最佳化
    @@ -967,7 +935,6 @@
     \n這會影響到網路與電池的使用,並會顯示指出 ${app_name} 正在監聽某事件的永久通知。
         無背景同步
         當應用程式在背景時,您將不會收到訊息通知。
    -
         探索
         管理您的探索設定。
         您未使用任何身份識別伺服器
    @@ -1036,7 +1003,6 @@
         此內容已被回報為不合適。 
     \n 
     \n如果您不想要看到從此使用者而來的更多內容,您可以忽略他們以隱藏他們的訊息。
    -
         整合
         使用整合管理員以管理機器人、橋接、小工具與貼紙包。
     \n整合管理員可以代表您接收設定資料,調整小工具、傳送聊天室邀請並設定權力等級。
    @@ -1251,7 +1217,6 @@
         驗證 %s
         已驗證 %s
         正在等待驗證 %s……
    -
         此聊天室中的訊息未端到端加密。
         此聊天室中的訊息有端到端加密。
     \n
    @@ -1394,7 +1359,6 @@
         列印並將其存放在安全的地方
         將其儲存在 USB 隨身碟或備份磁碟上
         將其複製到您的私人雲端儲存空間
    -
         加密已啟用
         在此聊天室中的訊息已端到端加密。取得更多資訊並在使用者的個人檔案中驗證他們。
         加密未啟用
    @@ -1789,7 +1753,6 @@
         隱藏進階
         顯示進階
         %2$d 中的 %1$d
    -
         給予同意
         撤銷我的同意
         您已同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。
    @@ -1880,8 +1843,6 @@
         轉移
         連線
         先諮詢
    -
    -
         通話中 (%1$s)
         查詢電話號碼時發生錯誤
         撥號鍵盤
    @@ -2075,7 +2036,6 @@
         輸入您想要探索的新伺服器名稱。
         加入新的伺服器
         您的伺服器
    -
         抱歉,試圖加入時發生錯誤:%s
         空間地址
         檢視與管理此空間的地址。
    @@ -2272,7 +2232,6 @@
         投票問題或主題
         建立投票
         投票
    -
         向 %s 傳送電子郵件與電話號碼
         您的通訊錄是私人的。要從您的通訊錄中探索使用者,我們需要您的權限來傳送聯絡人資訊到您的身份識別伺服器。
         已登出工作階段!
    @@ -2411,4 +2370,6 @@
         
             %d 伺服器 ACL 變更
         
    +    %1$s 與 %2$s
    +    %1$s、%2$s 與其他人
     
    \ No newline at end of file
    
    From cc7545b31fe1900f44b5d33cb4e6246afce3ee62 Mon Sep 17 00:00:00 2001
    From: libexus 
    Date: Tue, 1 Mar 2022 15:36:02 +0000
    Subject: [PATCH 0044/1430] Translated using Weblate (German)
    
    Currently translated at 100.0% (51 of 51 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/de/
    ---
     fastlane/metadata/android/de-DE/changelogs/40104000.txt | 2 ++
     fastlane/metadata/android/de-DE/changelogs/40104020.txt | 2 ++
     2 files changed, 4 insertions(+)
     create mode 100644 fastlane/metadata/android/de-DE/changelogs/40104000.txt
     create mode 100644 fastlane/metadata/android/de-DE/changelogs/40104020.txt
    
    diff --git a/fastlane/metadata/android/de-DE/changelogs/40104000.txt b/fastlane/metadata/android/de-DE/changelogs/40104000.txt
    new file mode 100644
    index 0000000000..37de3cb4a2
    --- /dev/null
    +++ b/fastlane/metadata/android/de-DE/changelogs/40104000.txt
    @@ -0,0 +1,2 @@
    +Neues: Erstelle Threads, damit dein Chatverlauf nicht zugespammt wird. Nachrichtenblasen.
    +Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.4.0
    diff --git a/fastlane/metadata/android/de-DE/changelogs/40104020.txt b/fastlane/metadata/android/de-DE/changelogs/40104020.txt
    new file mode 100644
    index 0000000000..6693401a24
    --- /dev/null
    +++ b/fastlane/metadata/android/de-DE/changelogs/40104020.txt
    @@ -0,0 +1,2 @@
    +Neues: Unterstützung für @room, Verbesserungen der Abstimmungen und weitere kleine Änderungen
    +Ganzer Changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.2
    
    From ee8e369294d84460cf59fb1787d20882d78936dc Mon Sep 17 00:00:00 2001
    From: Glandos 
    Date: Mon, 28 Feb 2022 19:52:43 +0000
    Subject: [PATCH 0045/1430] Translated using Weblate (French)
    
    Currently translated at 100.0% (51 of 51 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/
    ---
     fastlane/metadata/android/fr-FR/changelogs/40103170.txt | 2 ++
     fastlane/metadata/android/fr-FR/changelogs/40103180.txt | 2 ++
     fastlane/metadata/android/fr-FR/changelogs/40104000.txt | 2 ++
     fastlane/metadata/android/fr-FR/changelogs/40104020.txt | 2 ++
     4 files changed, 8 insertions(+)
     create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40103170.txt
     create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40103180.txt
     create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40104000.txt
     create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40104020.txt
    
    diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103170.txt b/fastlane/metadata/android/fr-FR/changelogs/40103170.txt
    new file mode 100644
    index 0000000000..c264ea3703
    --- /dev/null
    +++ b/fastlane/metadata/android/fr-FR/changelogs/40103170.txt
    @@ -0,0 +1,2 @@
    +Principaux changements pour cette version : envoyer votre position dans n'importe quel salon. Éditer un sondage.
    +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.17
    diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103180.txt b/fastlane/metadata/android/fr-FR/changelogs/40103180.txt
    new file mode 100644
    index 0000000000..0b8a9542a5
    --- /dev/null
    +++ b/fastlane/metadata/android/fr-FR/changelogs/40103180.txt
    @@ -0,0 +1,2 @@
    +Principaux changements pour cette version : envoyer votre position dans n'importe quel salon. Éditer un sondage.
    +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.18
    diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104000.txt b/fastlane/metadata/android/fr-FR/changelogs/40104000.txt
    new file mode 100644
    index 0000000000..eaced9e3f4
    --- /dev/null
    +++ b/fastlane/metadata/android/fr-FR/changelogs/40104000.txt
    @@ -0,0 +1,2 @@
    +Principaux changements pour cette version : Implémentation initial des fils de discussion. Bulles de messages.
    +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.0
    diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104020.txt b/fastlane/metadata/android/fr-FR/changelogs/40104020.txt
    new file mode 100644
    index 0000000000..068b4aac43
    --- /dev/null
    +++ b/fastlane/metadata/android/fr-FR/changelogs/40104020.txt
    @@ -0,0 +1,2 @@
    +Principaux changements pour cette version : Ajout du support pour @room et des sondages non terminé parmi plein d'autres changements mineurs.
    +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.2
    
    From 6e805beeedc41f9a0fdf7c94d21ae4009955f134 Mon Sep 17 00:00:00 2001
    From: SPiRiT 
    Date: Wed, 2 Mar 2022 21:02:50 +0000
    Subject: [PATCH 0046/1430] Translated using Weblate (Hebrew)
    
    Currently translated at 81.2% (1753 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
    ---
     vector/src/main/res/values-iw/strings.xml | 95 +++++++----------------
     1 file changed, 30 insertions(+), 65 deletions(-)
    
    diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
    index 1fea4fb060..bc0ab0ff34 100644
    --- a/vector/src/main/res/values-iw/strings.xml
    +++ b/vector/src/main/res/values-iw/strings.xml
    @@ -82,7 +82,6 @@
         ערכת נושא שחורה
         ערכת נושא כהה
         ערכת נושא בהירה
    -
         חדרים
         אנשי קשר מטריקס בלבד
         שיחות
    @@ -125,11 +124,9 @@
         זו אינה כתובת שרת מטריקס חוקית
         אנא הכנס כתובת תקינה
         אנא עיין וקבל את המדיניות של שרת בית זה:
    -
         אימות כתובת הדוא\"ל נכשל: ודא שלחצת על הקישור בדוא\"ל
         יש להזין את כתובת הדוא\"ל המקושרת לחשבונך.
         שרת הבית רוצה לוודא שאתה לא רובוט
    -
         שכחת סיסמה\?
         מספר הטלפון הזה כבר קיים ומעודכן במערכת.
         כתובת הדוא\"ל הזו כבר קיימת ומוגדרת במערכת.
    @@ -162,7 +159,6 @@
         שיחת האלמנט נכשלה
         האם אתה בטוח שברצונך להתחיל שיחת וידאו\?
         האם אתה בטוח שברצונך להתחיל שיחה קולית\?
    -
         עבור להודעה שלא נקראה
         חברי רשימה
         דחייה
    @@ -173,14 +169,10 @@
         כן
         אפשר הרשאה לגשת לאנשי הקשר שלך.
         כדי לסרוק קוד QR, עליך לאפשר גישה למצלמה.
    -
         אלמנט זקוק להרשאה כדי לגשת למצלמה ולמיקרופון שלך כדי לבצע שיחות וידאו.
     \n
     \nאנא אפשר גישה בחלונות הקופצים הבאים כדי להיות מסוגל לבצע את השיחה.
    -
         אלמנט זקוק להרשאה כדי לגשת למיקרופון שלך כדי לבצע שיחות שמע.
    -
    -
         מידע
         הצד המרוחק לא הצליח להרים.
         שיחת וידאו מתבצעת …
    @@ -235,7 +227,6 @@
             %d שניות
         
         עיכוב בין כל סינכרון
    -
         פסק-זמן לבקשה לסנכרון
         החל באתחול
         לא תקבל הודעה על הודעות נכנסות כאשר האפליקציה ברקע.
    @@ -310,7 +301,6 @@
         סנן משתמשים מודרים
         סנן חברים מהחדר
         חפש
    -
         
             %d נבחר
             %d נבחרים
    @@ -410,20 +400,14 @@
         תמונת פרופיל
         הוסף למסך הבית
         כל ההודעות
    -
         האם ברצונך לעזוב את החדר\?
         עזוב חדר
    -
    -
    -
    -
         
             %d חבר
             %d חברים
             %d חברים
             %d חברים
         
    -
         התחל אימות
         מושב לא מאומת מבקש מפתחות הצפנה.
     \nשם מושב: %1$s
    @@ -523,7 +507,6 @@
             %d חדרים מועטים
             %d חדרים אחרים
         
    -
         
             הודעת התראה %d שלא נקראה
             %d הודעות התראה שלא נקראו
    @@ -534,7 +517,6 @@
         כל החדרים בשרת %s
         כתובת אתר של שרת בית
         בחר מדריך חדרים
    -
         אם הם לא תואמים, אבטחת התקשורת שלך עלולה להיפגע.
         אשרו על ידי השוואה בין הדברים הבאים להגדרות המשתמש בפגישה האחרת שלכם:
         אמת
    @@ -556,7 +538,6 @@
         נהל גיבוי מפתח
         שחזור הודעות מוצפנות
         מפתחות יוצאו בהצלחה
    -
         אנא צור משפט סיסמה להצפנת המפתחות המיוצאים. יהיה עליך להזין את אותו ביטוי סיסמה כדי שתוכל לייבא את המפתחות.
         יצא
         יצא מפתחות לקובץ מקומי
    @@ -567,7 +548,6 @@
         שם ציבורי
         שגיאת פענוח
         ערכת נושא
    -
         ביטול ההגדרה ככתובת הראשית
         הגדר ככתובת ראשית
         אלה תכונות ניסיוניות שעשויות להישבר בדרכים לא צפויות. השתמש בזהירות.
    @@ -690,8 +670,6 @@
         סיבה: %1$s
         חסום על ידי %2$s מ- %1$s
         אתה נבעט מ- %1$s על ידי %2$s
    -
    -
         הוזמנו
         חדרים
         בית
    @@ -748,7 +726,6 @@
         לעולם אל תאבד הודעות מוצפנות
         להגן מפני אובדן גישה להודעות ונתונים מוצפנים
         גיבוי מאובטח
    -
         מחק גיבוי למחוק את מפתחות ההצפנה המגובים שלך מהשרת\? לא תוכל עוד להשתמש במפתח השחזור שלך כדי לקרוא את היסטוריית ההודעות המוצפנת.
         מחק את הגיבוי
         בודק מצב גיבוי
    @@ -794,7 +771,6 @@
         נראה שכבר יש לך גיבוי מפתח הגדרה מהפעלה אחרת. האם אתה רוצה להחליף אותו לזה שאתה יוצר\?
         גיבוי כבר קיים בשרת הבית שלך
         מפתח השחזור נשמר.
    -
         שמירת קובץ בשם
         שיתוף
         שמור מפתח שחזור
    @@ -998,7 +974,6 @@
         כל ההודעות
         כל ההודעות (רועשות)
         התעלם ממשתמש זה
    -
         תוכן זה דווח כבלתי הולם.
     \n
     \nאם אינך רוצה לראות תוכן נוסף ממשתמש זה, תוכל להתעלם ממנו כדי להסתיר את ההודעות שלו.
    @@ -1058,7 +1033,6 @@
         אנא הזן את כתובת ה- URL של שרת הזהות
         לא ניתן היה להתחבר לשרת זהות
         הזן כתובת אתר של שרת זהות
    -
         תן הסכמה
         בטל את הסכמתי
         נתת את הסכמתך לשלוח מיילים ומספרי טלפון לשרת זהות זה כדי לגלות משתמשים אחרים מאנשי הקשר שלך.
    @@ -1157,7 +1131,6 @@
     \nההודעות שלך מאובטחות במנעולים ורק לך ולמקבל יש את המפתחות הייחודיים לפתיחתם.
         ההודעות כאן אינן מוצפנות מקצה לקצה.
         הודעות בחדר זה אינן מוצפנות מקצה לקצה.
    -
         ממתין ל- %s…
         %s מאומת
         אמת. את %s
    @@ -1219,11 +1192,8 @@
         שגיאה לא ידועה
         %s רוצה לאמת את ההפעלה שלך
         בקשת אימות
    -
    -
         הבנתי
         מאומת!
    -
         חתימה
         אלגוריתם
         גירסה
    @@ -1253,7 +1223,6 @@
         הצג תוכן בהתראות
         קוד PIN הוא הדרך היחידה לפתוח את Element.
         אפשר ביומטריה ספציפית למכשירים, כמו טביעות אצבע וזיהוי פנים.
    -
         גיבוי מאובטח
         הוסף לחצן במלחין ההודעות כדי לפתוח מקלדת אימוג\'י
         הצג מקלדת אימוג\'י
    @@ -1284,11 +1253,9 @@
         צפה ועדכן את התפקידים הנדרשים לשינוי חלקים שונים בחדר.
         הרשאות חדרים
         חדר זה אינו ציבורי. לא תוכל להצטרף שוב ללא הזמנה.
    -
         (%%%s)התקדמות
         אנשים
         מועדפים
    -
         ברירת מחדל מערכת
         קפצו לקבלת קריאה
         הודעה ישירה
    @@ -1370,7 +1337,6 @@
         שרת ביתי
         מחובר כ
         הזדהות
    -
         נראה לאחרונה
         עדכן שם ציבורי
         שם ציבורי
    @@ -1378,7 +1344,6 @@
         אלמנט אוסף ניתוח אנונימי כדי לאפשר לנו לשפר את היישום.
         שלח נתוני ניתוח
         ניתוח נתונים
    -
         נהל את הגדרות הגילוי שלך.
         תַגלִית
         הפוך את המשתמש שלי ללא פעיל
    @@ -1568,7 +1533,6 @@
         אם תבטל עכשיו, אתה עלול לאבד הודעות ונתונים מוצפנים אם תאבד את הגישה לכניסות שלך.
     \n
     \nאתה יכול גם להגדיר גיבוי מאובטח ולנהל את המפתחות שלך בהגדרות.
    -
         העתק אותו לאחסון הענן האישי שלך
         שמור אותו במפתח USB או בכונן גיבוי
         הדפיסו ואחסנו במקום בטוח
    @@ -1731,55 +1695,55 @@
         סיימת את השיחה.
         %s סיים את השיחה.
         ענית לשיחה.
    -    %s שנה לשיחה.
    +    %s ענה לשיחה.
         שלחת מידע לביצוע שיחה.
         %s שלח מידע לביצוע שיחה.
         ביצעת שיחת אודיו.
    -    %s ביצע שיחת אודיו.
    +    %s ביצע/ה שיחה קולית.
         ביצעת שיחת וידאו.
    -    %s ביצע שיחת וידאו .
    +    %s ביצע/ה שיחת וידאו.
         שינית את שם החדר ל: %1$s
         %1$s שינה את שם החדר ל: %2$s
    -    שינית את דמות החדר
    -    %1$s שינה את דמות החדר
    -    שינית את השם ל: %1$s
    +    שינית את תמונת החדר
    +    %1$s שינה את תמונת החדר
    +    שינית את הנושא ל: %1$s
         %1$s שינה את הנושא ל: %2$s
    -    ביטלתם את שם התצוגה שלכם (שם קודם %1$s)
    +    הסרת את שם התצוגה שלך (הוא היה %1$s)
         %1$sהורידו את שם התצוגה שלהם (היה קודם %2$s)
         שינית את שמך מ %1$s ל %2$s
         %1$s שינו את שמותיהם מ %2$s ל %3$s
         שינית את שמך ל %1$s
         %1$s שינה את שמו ל %2$s
    -    שינית את דמותך באפליקציה
    -    %1$s שינה את דמותו
    +    שינית תמונת פרופיל
    +    %1$s שינה תמונת פרופיל
         משכת את הזמנתו של %1$s\'s
    -    %1$s משך את הזמנתו של %2$s\'s
    +    %1$s ביטל/ה את ההזמנה של %2$s\'s
         חסמת את %1$s
    -    %1$s חסם את%2$s
    -    חסמת את %1$s
    -    %1$s הסיר מחסימה את %2$s
    +    %1$s חסם/ה את %2$s
    +    הסרת את החסימה של %1$s
    +    %1$s הסיר/ה מחסימה את %2$s
         הרחקת את %1$s
    -    %1$s הרחיק את%2$s
    +    %1$s הרחיק/ה את %2$s
         דחית את ההזמנה
         %1$s דחה את ההזמנה
         עזבת את החדר
    -    %1$s עזב את החדר
    +    %1$s עזב/ה את החדר
         עזבת את החדר
    -    %1$s עזב את החדר
    +    %1$s עזב/ה את החדר
         הצטרפת
    -    %1$s הצטרף
    +    %1$s הצטרף/ה
         הצטרפת לחדר
    -    %1$s הצטרף לחדר
    -    %1$s הזמין אותך
    +    %1$s הצטרף/ה לחדר
    +    %1$s הזמין/ה אותך
         הזמנת את %1$s
    -    %1$s הזמין %2$s
    +    %1$s הזמין/ה את %2$s
         יצרת את הדיון
    -    %1$s יצר את הדיון
    +    %1$s יצר/ה את הדיון
         יצרת את החדר
    -    %1$s יצר את החדר
    +    %1$s יצר/ה את החדר
         הזמנתך
    -    %s הזמנה
    -    כל חברי החדר, מהנקודה בה הם הוזמנו.
    +    ההזמנה של %s
    +    כל חברי החדר, מהרגע שבו הוזמנו.
         את/ה הפכת הודעות עתידיות לגלויות בפני %1$s
         %1$s הפך הודעות עתידיות לגלויות בפני %2$s
         חסמת את %1$s. סיבה: %2$s
    @@ -1830,7 +1794,6 @@
     \nממתין לתגובת השרת…
         חדר ריק (היה %s)
         חדר ריק
    -
         
             %1$s, %2$s, %3$s ו-%4$d אחר
             %1$s, %2$s, %3$s ו-%4$d אחרים
    @@ -1894,10 +1857,10 @@
         שדרגת כאן.
         %s שודרג כאן.
         שדרגת את החדר הזה.
    -    %s שדרג את החדר הזה.
    +    %s שידרג/ה חדר זה.
         כל אחד.
         כל חברי החדר.
    -    כל חברי החדר, מהנקודה בה הצטרפו.
    +    כל חברי החדר, מהרגע שבו הצטרפו.
         מפה
         שתף מיקום
         מיקום
    @@ -1924,8 +1887,8 @@
         מיפוי מיקומי משתמשים בציר הזמן
         לאחר ההפעלה, תוכל לשלוח את המיקום שלך לכל חדר
         אפשר שיתוף מיקום
    -    לפתוח בעזרת
    -    ${app_name} לא הצליח לגשת למיקום שלך. בבקשה נסה שוב מאוחר יותר.
    +    פתח באמצעות
    +    ${app_name} לא הצליח לגשת למיקום שלך. אנא נסה שוב מאוחר יותר.
         ${app_name} לא הצליח לגשת למיקום שלך
         שתף מיקום
         אפשר התראת דוא\"ל עבור %s
    @@ -2096,4 +2059,6 @@
         %1$s משך את ההזמנה של %2$s. סיבה: %3$s
         קיבלת את ההזמנה עבור %1$s. סיבה: %2$s
         %1$s קיבל את ההזמנה עבור %2$s. סיבה: %3$s
    +    שיתפו את מיקומם
    +    מיקום
     
    \ No newline at end of file
    
    From ed7d1927f5466f61d231cccc5a0dc87a357ebf4a Mon Sep 17 00:00:00 2001
    From: sagi korin 
    Date: Wed, 2 Mar 2022 15:41:11 +0000
    Subject: [PATCH 0047/1430] Translated using Weblate (Hebrew)
    
    Currently translated at 81.2% (1753 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
    ---
     vector/src/main/res/values-iw/strings.xml | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
    index bc0ab0ff34..57a8cf6630 100644
    --- a/vector/src/main/res/values-iw/strings.xml
    +++ b/vector/src/main/res/values-iw/strings.xml
    @@ -2061,4 +2061,10 @@
         %1$s קיבל את ההזמנה עבור %2$s. סיבה: %3$s
         שיתפו את מיקומם
         מיקום
    +    משתמשים
    +    הצג פחות
    +    דלג על השאלה
    +    יש לי כבר חשבון
    +    צור חשבון
    +    תקשורת מאובטחת.
     
    \ No newline at end of file
    
    From e8024688f4cbc3110323c930b4a01dac207fbec8 Mon Sep 17 00:00:00 2001
    From: Besnik Bleta 
    Date: Wed, 2 Mar 2022 09:37:09 +0000
    Subject: [PATCH 0048/1430] Translated using Weblate (Albanian)
    
    Currently translated at 100.0% (51 of 51 strings)
    
    Translation: Element Android/Element Android Store
    Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/
    ---
     fastlane/metadata/android/sq/changelogs/40104000.txt | 2 ++
     fastlane/metadata/android/sq/changelogs/40104020.txt | 2 ++
     2 files changed, 4 insertions(+)
     create mode 100644 fastlane/metadata/android/sq/changelogs/40104000.txt
     create mode 100644 fastlane/metadata/android/sq/changelogs/40104020.txt
    
    diff --git a/fastlane/metadata/android/sq/changelogs/40104000.txt b/fastlane/metadata/android/sq/changelogs/40104000.txt
    new file mode 100644
    index 0000000000..f917c7c0cb
    --- /dev/null
    +++ b/fastlane/metadata/android/sq/changelogs/40104000.txt
    @@ -0,0 +1,2 @@
    +Ndryshime kryesore në këtë version: Sendërtimi fillestar i mesazheve në rrjedha. Flluska mesazhesh.
    +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.0
    diff --git a/fastlane/metadata/android/sq/changelogs/40104020.txt b/fastlane/metadata/android/sq/changelogs/40104020.txt
    new file mode 100644
    index 0000000000..2fbe4f2bf6
    --- /dev/null
    +++ b/fastlane/metadata/android/sq/changelogs/40104020.txt
    @@ -0,0 +1,2 @@
    +Ndryshimet kryesore në këtë version: shtim mbulimi për @room dhe për pyetësorë jopublikë, mes mjaft ndryshimesh të tjera të vockla.
    +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.2
    
    From 82e0f4a1a9775780a5c32f4be56cb8a7de8900d7 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Tue, 1 Mar 2022 13:17:25 +0000
    Subject: [PATCH 0049/1430] Run sonarqube as part of nightly build
    
    ---
     .github/workflows/nightly.yml | 30 +++++++++++++++++++++++++++---
     1 file changed, 27 insertions(+), 3 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 0a12587e84..0b0ab9f922 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -14,7 +14,6 @@ env:
         -Porg.gradle.jvmargs=-Xmx4g
         -Porg.gradle.parallel=false
         -PallWarningsAsErrors=false
    -
     jobs:
       # Build Android Tests [Matrix SDK]
       build-android-test-matrix-sdk:
    @@ -263,8 +262,6 @@ jobs:
         # No concurrency required, runs every time on a schedule.
         steps:
           - uses: actions/checkout@v2
    -        with:
    -          ref: develop
           - name: Set up Python 3.8
             uses: actions/setup-python@v3
             with:
    @@ -311,12 +308,39 @@ jobs:
                 emulator.log
                 failure_screenshots/
     
    +  sonarqube:
    +    runs-on: macos-latest
    +    if: always()
    +    needs:
    +      - integration-tests
    +      - ui-tests
    +#      - unit-tests  TODO: code coverage from here too
    +      - build-android-test-matrix-sdk 
    +      - build-android-test-app
    +    steps:
    +      - uses: actions/checkout@v2
    +      - uses: actions/cache@v2
    +        with:
    +          path: |
    +            ~/.gradle/caches
    +            ~/.gradle/wrapper
    +          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    +          restore-keys: |
    +            ${{ runner.os }}-gradle-
    +      - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
    +        env:
    +          - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    +
     # Notify the channel about scheduled runs, do not notify for manually triggered runs
       notify:
         runs-on: ubuntu-latest
         needs:
           - integration-tests
           - ui-tests
    +#      - unit-tests
    +      - build-android-test-matrix-sdk 
    +      - build-android-test-app
    +      - sonarqube
         if: always() && github.event_name != 'workflow_dispatch'
         # No concurrency required, runs every time on a schedule.
         steps:
    
    From 5ea917f74b1dfddbec6f7f89ed4f46d21ce7c22c Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Tue, 1 Mar 2022 14:37:36 +0000
    Subject: [PATCH 0050/1430] jobs.step.env is a map not a list
    
    ---
     .github/workflows/nightly.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 0b0ab9f922..5b61b6b3f4 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -329,7 +329,7 @@ jobs:
                 ${{ runner.os }}-gradle-
           - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
             env:
    -          - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    +          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
     
     # Notify the channel about scheduled runs, do not notify for manually triggered runs
       notify:
    
    From fd065dbbb90a387d122a4f6ce28903e35817166e Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Tue, 1 Mar 2022 14:47:41 +0000
    Subject: [PATCH 0051/1430] Update build.gradle
    
    Note password secret name.
    ---
     build.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/build.gradle b/build.gradle
    index 9cae9e7e70..0a41ac3625 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -109,7 +109,7 @@ apply plugin: 'org.sonarqube'
     
     // To run a sonar analysis:
     // Run './gradlew sonarqube -Dsonar.login='
    -// The SONAR_KEY is stored in passbolt
    +// The SONAR_KEY is stored in passbolt as Token Sonar Cloud Bma
     
     sonarqube {
         properties {
    
    From 0ce787f20be7dda81dff55c8c86f4f71735e0896 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Wed, 2 Mar 2022 10:31:07 +0000
    Subject: [PATCH 0052/1430] Ensure we run on java 11 on macos
    
    ---
     .github/workflows/nightly.yml | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 5b61b6b3f4..aaef180de6 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -319,6 +319,10 @@ jobs:
           - build-android-test-app
         steps:
           - uses: actions/checkout@v2
    +      - uses: actions/setup-java@v2
    +        with:
    +          distribution: 'adopt'
    +          java-version: '11'
           - uses: actions/cache@v2
             with:
               path: |
    
    From 33b170077ea5d342d35a550e4808abdd8f374d18 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Thu, 3 Mar 2022 13:49:53 +0200
    Subject: [PATCH 0053/1430] force refresh home server capabilities
    
    ---
     .../sdk/internal/database/migration/MigrateSessionTo026.kt      | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
    index ac097c916b..f108a91ecf 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt
    @@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti
     import org.matrix.android.sdk.internal.database.model.RoomEntityFields
     import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
     import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
    +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
     import org.matrix.android.sdk.internal.util.database.RealmMigrator
     
     /**
    @@ -59,5 +60,6 @@ class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) {
     
             realm.schema.get("HomeServerCapabilitiesEntity")
                     ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java)
    +                ?.forceRefreshOfHomeServerCapabilities()
         }
     }
    
    From 719e254bb46875965c553c6cc01849c6044fa17a Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Thu, 3 Mar 2022 13:51:41 +0200
    Subject: [PATCH 0054/1430] Format Code
    
    ---
     .../sdk/internal/session/room/timeline/LoadTimelineStrategy.kt  | 2 +-
     .../android/sdk/internal/session/room/timeline/TimelineChunk.kt | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
    index a26008369a..a9e7b3bcdc 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
    @@ -198,7 +198,7 @@ internal class LoadTimelineStrategy(
                 }
             }
             if (mode is Mode.Thread) {
    -            return timelineChunk?.loadMoreThread(count, Timeline.Direction.BACKWARDS) ?: LoadMoreResult.FAILURE
    +            return timelineChunk?.loadMoreThread(count) ?: LoadMoreResult.FAILURE
             }
             return timelineChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
    index 7ab2e17b9d..04a5256510 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
    @@ -169,7 +169,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
          * This function will fetch more live thread timeline events using the /relations api. It will
          * always fetch results, while we want our data to be up to dated.
          */
    -    suspend fun loadMoreThread(count: Int, direction: Timeline.Direction): LoadMoreResult {
    +    suspend fun loadMoreThread(count: Int, direction: Timeline.Direction = Timeline.Direction.BACKWARDS): LoadMoreResult {
             val rootThreadEventId = timelineSettings.rootThreadEventId ?: return LoadMoreResult.FAILURE
             return if (direction == Timeline.Direction.BACKWARDS) {
                 try {
    
    From a77637b751a3cadd8c1e6cffdcac2d1b13aaeb32 Mon Sep 17 00:00:00 2001
    From: Suguru Hirahara 
    Date: Thu, 3 Mar 2022 13:05:29 +0000
    Subject: [PATCH 0055/1430] Translated using Weblate (Japanese)
    
    Currently translated at 98.3% (2121 of 2157 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
    ---
     vector/src/main/res/values-ja/strings.xml | 36 +++++++++++------------
     1 file changed, 18 insertions(+), 18 deletions(-)
    
    diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
    index fbd6540dd6..c1f3304fb8 100644
    --- a/vector/src/main/res/values-ja/strings.xml
    +++ b/vector/src/main/res/values-ja/strings.xml
    @@ -1489,8 +1489,8 @@
         画像
         スクリーンショット
         接続
    -    投票
    -    投票
    +    アンケート
    +    アンケート
         コード
         役割
         送信
    @@ -1515,9 +1515,9 @@
         警告!
         位置情報
         メディア
    -    投票終了
    -    投票を終了しますか?
    -    投票を削除
    +    アンケート終了
    +    アンケートを終了しますか?
    +    アンケートを削除
         スペースを作成
         スペースに参加
         スペースを退出
    @@ -1531,10 +1531,10 @@
         非公開のルームをアップグレード
         音声メッセージを一時停止
         このメールアドレスをアカウントにリンク
    -    投票を編集
    +    アンケートを編集
         地図
    -    投票を終了
    -    投票を終了
    +    アンケートを終了
    +    アンケートを終了
         選択肢%1$d
         選択肢を作成
         位置情報
    @@ -1549,7 +1549,7 @@
         ファイルをアップロード
         連絡先を開く
         ステッカーを送信
    -    投票を作成
    +    アンケートを作成
         位置情報を共有
         スペースに関する変更を行うために必要な役割を更新する権限がありません
         スペースに関する変更を行うために必要な役割を選択
    @@ -1587,8 +1587,8 @@
         ルームの暗号化の有効化
         スペースのメインアドレスの変更
         スペースのアバターの変更
    -    投票を作成
    -    投票を作成
    +    アンケートを作成
    +    アンケートを作成
         暗号化が正しく設定されていないため、メッセージを送ることができません。クリックして設定を開いてください。
         暗号化が正しく設定されていないため、メッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
         %2$dの%1$d
    @@ -1741,12 +1741,12 @@
         続行するには%sを入力してください
         有効なリカバリーキーではありません
         リカバリーキーを入力してください
    -    投票の種類
    -    実施中の投票
    +    アンケートの種類
    +    投票の際に結果を公開
         投票する
         投票した人には、投票の際に即座に結果が表示されます
    -    終了した投票
    -    結果は投票を終了した後でのみ明らかにされます
    +    アンケートの終了後に結果を公開
    +    結果はアンケートを終了した後でのみ明らかにされます
         以下で開く
         暗号化のアップグレードが利用可能です
         SSSSキーをリカバリーキーから生成しています
    @@ -1762,7 +1762,7 @@
         
             %1$d個の投票
         
    -    投票を締め切り、投票の最終結果を表示します。
    +    アンケートを締め切り、最終結果を表示します。
         招待者のみ参加可能。個人やチームに最適
         スペースを作成
         連絡先をスペースに招待
    @@ -1789,7 +1789,7 @@
         音声メッセージを録音しています
         録音を削除
         音声メッセージがアクティブの間は返信や編集はできません
    -    この投票を削除してよろしいですか?一度削除すると復元することはできません。
    +    このアンケートを削除してよろしいですか?一度削除すると復元することはできません。
         共有データの取り扱いに失敗しました
         回転とクロップ
         ルームを探索
    @@ -1815,7 +1815,7 @@
     \nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
         %1$sにより%2$sに
         質問あるいはトピック
    -    投票の質問あるいはトピック
    +    アンケートの質問あるいはトピック
         少々お待ちください。少し時間がかかるかもしれません。
         ルームのバージョン 👓
         不安定
    
    From 4254f4606535f6899e0cc130683cfbbfc46fa7e8 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 3 Mar 2022 17:59:51 +0300
    Subject: [PATCH 0056/1430] Support scrolling playback on timeline.
    
    ---
     .../home/room/detail/TimelineFragment.kt      |  8 ++++++
     .../detail/composer/MessageComposerAction.kt  |  2 ++
     .../composer/MessageComposerViewModel.kt      | 16 +++++++++++-
     .../detail/composer/VoiceMessageHelper.kt     |  8 ++++++
     .../timeline/TimelineEventController.kt       |  2 ++
     .../timeline/factory/MessageItemFactory.kt    | 11 ++++++++
     .../detail/timeline/item/MessageVoiceItem.kt  | 25 +++++++++++++++++++
     7 files changed, 71 insertions(+), 1 deletion(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    index 2da69bbe6c..d019cb1777 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    @@ -2051,6 +2051,14 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    +    override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage))
    +    }
    +
    +    override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage))
    +    }
    +
         private fun onShareActionClicked(action: EventSharedAction.Share) {
             when (action.messageContent) {
                 is MessageTextContent           -> shareText(requireContext(), action.messageContent.body)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    index 10cef39942..daa5631d84 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    @@ -40,4 +40,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
         data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
         object PlayOrPauseRecordingPlayback : MessageComposerAction()
         data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
    +    data class VoiceWaveformTouchedUp(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformMovedTo(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index 0d90227168..ccb51d3796 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    @@ -108,7 +108,9 @@ class MessageComposerViewModel @AssistedInject constructor(
                 is MessageComposerAction.EndAllVoiceActions             -> handleEndAllVoiceActions(action.deleteRecord)
                 is MessageComposerAction.InitializeVoiceRecorder        -> handleInitializeVoiceRecorder(action.attachmentData)
                 is MessageComposerAction.OnEntersBackground             -> handleEntersBackground(action.composerText)
    -        }
    +            is MessageComposerAction.VoiceWaveformTouchedUp         -> handleVoiceWaveformTouchedUp(action)
    +            is MessageComposerAction.VoiceWaveformMovedTo           -> handleVoiceWaveformMovedTo(action)
    +        }.exhaustive
         }
     
         private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState {
    @@ -861,6 +863,18 @@ class MessageComposerViewModel @AssistedInject constructor(
             voiceMessageHelper.pauseRecording()
         }
     
    +    private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    +        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    +        val toMillisecond = (action.percentage * duration).toInt()
    +        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +    }
    +
    +    private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    +        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    +        val toMillisecond = (action.percentage * duration).toInt()
    +        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +    }
    +
         private fun handleEntersBackground(composerText: String) {
             val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
             if (isVoiceRecording) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    index f9dfecd1f5..b6a8dc2cd5 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    @@ -174,6 +174,14 @@ class VoiceMessageHelper @Inject constructor(
             stopPlaybackTicker()
         }
     
    +    fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) {
    +        val percentage = toMillisecond.toFloat() / totalDuration
    +        playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
    +
    +        stopPlayback()
    +        playbackTracker.pausePlayback(id)
    +    }
    +
         private fun startRecordingAmplitudes() {
             amplitudeTicker?.stop()
             amplitudeTicker = CountUpTimer(50).apply {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    index 2ac592797c..3965afdbaa 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    @@ -138,6 +138,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    +        fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
         }
     
         interface ReactionPillCallback {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index da97cf6984..8b0b43009d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -357,11 +357,22 @@ class MessageItemFactory @Inject constructor(
                 }
             }
     
    +        val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
    +            override fun onWaveformTouchedUp(percentage: Float) {
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage)
    +            }
    +
    +            override fun onWaveformMovedTo(percentage: Float) {
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, percentage)
    +            }
    +        }
    +
             return MessageVoiceItem_()
                     .attributes(attributes)
                     .duration(messageContent.audioWaveformInfo?.duration ?: 0)
                     .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
                     .playbackControlButtonClickListener(playbackControlButtonClickListener)
    +                .waveformTouchListener(waveformTouchListener)
                     .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
                     .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
                     .izDownloaded(session.fileService().isFileInCache(
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    index 82400a431d..d1c134a743 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item
     import android.content.res.ColorStateList
     import android.graphics.Color
     import android.text.format.DateUtils
    +import android.view.MotionEvent
     import android.view.View
     import android.view.ViewGroup
     import android.widget.ImageButton
    @@ -38,6 +39,11 @@ import im.vector.app.features.voice.AudioWaveformView
     @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
     abstract class MessageVoiceItem : AbsMessageItem() {
     
    +    interface WaveformTouchListener {
    +        fun onWaveformTouchedUp(percentage: Float)
    +        fun onWaveformMovedTo(percentage: Float)
    +    }
    +
         @EpoxyAttribute
         var mxcUrl: String = ""
     
    @@ -62,6 +68,9 @@ abstract class MessageVoiceItem : AbsMessageItem() {
         @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
         var playbackControlButtonClickListener: ClickListener? = null
     
    +    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    +    var waveformTouchListener: WaveformTouchListener? = null
    +
         @EpoxyAttribute
         lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker
     
    @@ -87,6 +96,20 @@ abstract class MessageVoiceItem : AbsMessageItem() {
                     holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle))
                 }
                 holder.voicePlaybackWaveform.summarize()
    +
    +            holder.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +                when (motionEvent.action) {
    +                    MotionEvent.ACTION_UP   -> {
    +                        val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                        waveformTouchListener?.onWaveformTouchedUp(percentage)
    +                    }
    +                    MotionEvent.ACTION_MOVE -> {
    +                        val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                        waveformTouchListener?.onWaveformMovedTo(percentage)
    +                    }
    +                }
    +                true
    +            }
             }
     
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    @@ -111,6 +134,8 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +
         private fun renderIdleState(holder: Holder, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)
    
    From 39bd437f759c36d2b8c59f5107b7c283c5cc8b31 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Thu, 3 Mar 2022 17:04:08 +0200
    Subject: [PATCH 0057/1430] Temp fix Realm crash
    
    ---
     .../session/room/timeline/SendingEventsDataSource.kt       | 7 ++++++-
     1 file changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    index b7a2cf2fce..4c5322d073 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    @@ -66,7 +66,12 @@ internal class RealmSendingEventsDataSource(
     
         private fun updateFrozenResults(sendingEvents: RealmList?) {
             // Makes sure to close the previous frozen realm
    -        frozenSendingTimelineEvents?.realm?.close()
    +        // TODO find a better way to avoid thread timeline crash:
    +        //  - Make RealmSendingEventsDataSource Singleton
    +        //  - Do not initialize RealmSendingEventsDataSource when we are in a thread timeline while
    +        //    we already have an instance from the main timeline
    +        //  - Close Main timeline before Opening a thread timeline
    +        // frozenSendingTimelineEvents?.realm?.close()
             frozenSendingTimelineEvents = sendingEvents?.freeze()
         }
     
    
    From 33648683654f731bd373282249b5958c5c13c340 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 15:05:39 +0000
    Subject: [PATCH 0058/1430] Use environment variable that is tied to project
     property
    
    ---
     .github/workflows/nightly.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index aaef180de6..1536f57be5 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -333,7 +333,7 @@ jobs:
                 ${{ runner.os }}-gradle-
           - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
             env:
    -          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    +          ORG_GRADLE_PROJECT_SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
     
     # Notify the channel about scheduled runs, do not notify for manually triggered runs
       notify:
    
    From 105f3dd93d1a389bd42e029eceb6d44fb011f4c2 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 16:34:26 +0000
    Subject: [PATCH 0059/1430] Correct name of environment variable
    
    ---
     .github/workflows/nightly.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index 1536f57be5..ebad9b7bc7 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -333,7 +333,7 @@ jobs:
                 ${{ runner.os }}-gradle-
           - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
             env:
    -          ORG_GRADLE_PROJECT_SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    +          ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
     
     # Notify the channel about scheduled runs, do not notify for manually triggered runs
       notify:
    
    From 49fbfe6811d46c2dcfb6d0a0c50442f14ce0220c Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 10:40:11 +0000
    Subject: [PATCH 0060/1430] Better codecov based on ouchadam's suggestion
    
    ---
     build.gradle               | 10 ++++
     coverage.gradle            | 97 ++++++++++++++++++++++++++++++++++++++
     dependencies_groups.gradle |  1 +
     3 files changed, 108 insertions(+)
     create mode 100644 coverage.gradle
    
    diff --git a/build.gradle b/build.gradle
    index 0a41ac3625..42d2abe2ce 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -105,6 +105,16 @@ task clean(type: Delete) {
         delete rootProject.buildDir
     }
     
    +def launchTask = getGradle()
    +        .getStartParameter()
    +        .getTaskRequests()
    +        .toString()
    +        .toLowerCase()
    +
    +if (launchTask.contains("codeCoverageReport".toLowerCase())) {
    +    apply from: 'coverage.gradle'
    +}
    +
     apply plugin: 'org.sonarqube'
     
     // To run a sonar analysis:
    diff --git a/coverage.gradle b/coverage.gradle
    new file mode 100644
    index 0000000000..098c67c26f
    --- /dev/null
    +++ b/coverage.gradle
    @@ -0,0 +1,97 @@
    +def excludes = [
    +        // DI graph
    +        '**/*Module.*',
    +        '**/*Module*.*',
    +
    +        // Android composables
    +        '**/*Screen*',
    +        '**/components/*',
    +        '**/*Compose*.*',
    +
    +        // Android framework
    +        '**/*Activity*',
    +        '**/*AndroidService*',
    +        '**/*Application*',
    +
    +        // Generated
    +        '**/*serializer*',
    +        '**/*Serializer*',
    +        "**/*request/*Companion*.*",
    +        '**/*QueriesImpl*',
    +        '**/*Db*',
    +        '**/Select*',
    +
    +        // Tmp until serializationx can ignore generated
    +        '**/Api*',
    +]
    +
    +def initializeReport(report, projects, classExcludes) {
    +    projects.each { project -> project.apply plugin: 'jacoco' }
    +    report.executionData { fileTree(rootProject.rootDir.absolutePath).include("**/build/jacoco/*.exec") }
    +
    +    report.reports {
    +        xml.enabled true
    +        html.enabled true
    +        csv.enabled false
    +    }
    +
    +    gradle.projectsEvaluated {
    +        def androidSourceDirs = []
    +        def androidClassDirs = []
    +
    +        projects.each { project ->
    +            switch (project) {
    +                case { project.plugins.hasPlugin("com.android.application") }:
    +                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
    +                    androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
    +                    androidSourceDirs.add("${project.projectDir}/src/main/java")
    +                    break
    +                case { project.plugins.hasPlugin("com.android.library") }:
    +                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/release")
    +                    androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
    +                    androidSourceDirs.add("${project.projectDir}/src/main/java")
    +                    break
    +                default:
    +                    report.sourceSets project.sourceSets.main
    +            }
    +        }
    +
    +        report.sourceDirectories.setFrom(report.sourceDirectories + files(androidSourceDirs))
    +        def classFiles = androidClassDirs.collect { files(it).files }.flatten()
    +        report.classDirectories.setFrom(files((report.classDirectories.files + classFiles).collect {
    +            fileTree(dir: it, excludes: classExcludes)
    +        }))
    +    }
    +}
    +
    +def collectProjects(predicate) {
    +    return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
    +}
    +
    +//task unitCodeCoverageReport(type: JacocoReport) {
    +//    outputs.upToDateWhen { false }
    +//    rootProject.apply plugin: 'jacoco'
    +//    def excludedProjects = [
    +//            'olm-stub',
    +//            'test-harness'
    +//    ]
    +//    def projects = collectProjects { !excludedProjects.contains(it.name) }
    +//    dependsOn { ["app:assembleDebug"] + projects*.test }
    +//    initializeReport(it, projects, excludes)
    +//}
    +//
    +//task harnessCodeCoverageReport(type: JacocoReport) {
    +//    outputs.upToDateWhen { false }
    +//    rootProject.apply plugin: 'jacoco'
    +//    def projects = collectProjects { true }
    +//    dependsOn { ["app:assembleDebug", project(":test-harness").test] }
    +//    initializeReport(it, projects, excludes)
    +//}
    +
    +task allCodeCoverageReport(type: JacocoReport) {
    +    outputs.upToDateWhen { false }
    +    rootProject.apply plugin: 'jacoco'
    +    def projects = collectProjects { true }
    +    dependsOn { projects*.test }
    +    initializeReport(it, projects, excludes)
    +}
    diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle
    index 7de8100469..4c976faa7c 100644
    --- a/dependencies_groups.gradle
    +++ b/dependencies_groups.gradle
    @@ -150,6 +150,7 @@ ext.groups = [
                             'org.ec4j.core',
                             'org.glassfish.jaxb',
                             'org.hamcrest',
    +                        'org.jacoco',
                             'org.jetbrains',
                             'org.jetbrains.intellij.deps',
                             'org.jetbrains.kotlin',
    
    From 76844b15721aaf26747a39cf9d3b94d9532a33ab Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 15:41:09 +0000
    Subject: [PATCH 0061/1430] Run codecoverage and pass to sonarqube upload for
     processing.
    
    ---
     .github/workflows/nightly.yml | 34 +++++++++++++++++++++++++++++-----
     build.gradle                  |  4 +++-
     2 files changed, 32 insertions(+), 6 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index ebad9b7bc7..d90dafd93b 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -308,15 +308,36 @@ jobs:
                 emulator.log
                 failure_screenshots/
     
    +  codecov-units:
    +    runs-on: macos-latest
    +    steps:
    +      - uses: actions/checkout@v2
    +      - uses: actions/setup-java@v2
    +        with: 
    +          distribution: 'adopt'
    +          java-version: '11'
    +      - uses: action/cache@v2
    +        with:
    +          path: |
    +            ~/.gradle/caches
    +            ~/.gradle/wrapper
    +          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    +          restore-keys: |
    +            ${{ runner.os }}-gradle-
    +      - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES
    +      - name: Upload Codecov data
    +        uses: actions/upload-artifact@v2
    +        if: always()
    +        with:
    +          name: codecov-xml
    +          path: |
    +            build/jacoco/*.xml
    +
       sonarqube:
         runs-on: macos-latest
         if: always()
         needs:
    -      - integration-tests
    -      - ui-tests
    -#      - unit-tests  TODO: code coverage from here too
    -      - build-android-test-matrix-sdk 
    -      - build-android-test-app
    +      - codecov-units
         steps:
           - uses: actions/checkout@v2
           - uses: actions/setup-java@v2
    @@ -331,6 +352,9 @@ jobs:
               key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
               restore-keys: |
                 ${{ runner.os }}-gradle-
    +      - uses: actions/download-artifact@v3
    +        with:
    +          artifact: codecov-xml # will restore to build/ as a local run would do
           - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
             env:
               ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
    diff --git a/build.gradle b/build.gradle
    index 42d2abe2ce..23cb479296 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -129,10 +129,12 @@ sonarqube {
             property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
             property "sonar.sourceEncoding", "UTF-8"
             property "sonar.links.homepage", "https://github.com/vector-im/element-android/"
    -        property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/element-android"
    +        property "sonar.links.ci", "https://github.com/vector-im/element-android/actions"
             property "sonar.links.scm", "https://github.com/vector-im/element-android/"
             property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
             property "sonar.organization", "new_vector_ltd_organization"
    +        property "sonar.java.coveragePlugin", "jacoco"
    +        property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml"
             property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
         }
     }
    
    From 54e23a2c55a1cb8bde237bb8d54d9d1a33735060 Mon Sep 17 00:00:00 2001
    From: Adam Brown 
    Date: Thu, 3 Mar 2022 16:15:44 +0000
    Subject: [PATCH 0062/1430] giving avatar/display name error dialogs human
     readable error messages - reuses the ErrorDialog logic which translates
     exceptions to human readable strings
    
    ---
     changelog.d/5418.feature                       |  1 +
     .../settings/VectorSettingsBaseFragment.kt     | 18 ------------------
     .../settings/VectorSettingsGeneralFragment.kt  | 18 +++++++++++++-----
     3 files changed, 14 insertions(+), 23 deletions(-)
     create mode 100644 changelog.d/5418.feature
    
    diff --git a/changelog.d/5418.feature b/changelog.d/5418.feature
    new file mode 100644
    index 0000000000..5e1efc8718
    --- /dev/null
    +++ b/changelog.d/5418.feature
    @@ -0,0 +1 @@
    +Improves settings error dialog messaging when changing avatar or display name fails
    \ No newline at end of file
    diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
    index dae234eecc..176909b48d 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
    @@ -148,24 +148,6 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
             }
         }
     
    -    /**
    -     * A request has been processed.
    -     * Display a toast if there is a an error message
    -     *
    -     * @param errorMessage the error message
    -     */
    -    protected fun onCommonDone(errorMessage: String?) {
    -        if (!isAdded) {
    -            return
    -        }
    -        activity?.runOnUiThread {
    -            if (errorMessage != null && errorMessage.isNotBlank()) {
    -                displayErrorDialog(errorMessage)
    -            }
    -            hideLoadingView()
    -        }
    -    }
    -
         protected fun displayErrorDialog(throwable: Throwable) {
             displayErrorDialog(errorFormatter.toHumanReadable(throwable))
         }
    diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
    index 767e555ede..ffb9fc4af4 100644
    --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
    @@ -329,7 +329,14 @@ class VectorSettingsGeneralFragment @Inject constructor(
                     session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString())
                 }
                 if (!isAdded) return@launch
    -            onCommonDone(result.fold({ null }, { it.localizedMessage }))
    +
    +            result.fold(
    +                    onSuccess = { hideLoadingView() },
    +                    onFailure = {
    +                        hideLoadingView()
    +                        displayErrorDialog(it)
    +                    }
    +            )
             }
         }
     
    @@ -466,14 +473,15 @@ class VectorSettingsGeneralFragment @Inject constructor(
                     val result = runCatching { session.setDisplayName(session.myUserId, value) }
                     if (!isAdded) return@launch
                     result.fold(
    -                        {
    +                        onSuccess = {
                                 // refresh the settings value
                                 mDisplayNamePreference.summary = value
                                 mDisplayNamePreference.text = value
    -                            onCommonDone(null)
    +                            hideLoadingView()
                             },
    -                        {
    -                            onCommonDone(it.localizedMessage)
    +                        onFailure = {
    +                            hideLoadingView()
    +                            displayErrorDialog(it)
                             }
                     )
                 }
    
    From 3bd4a4ccd3ece851ac3de5c4c4e1e11a406efc33 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 3 Mar 2022 19:54:13 +0300
    Subject: [PATCH 0063/1430] Fix voice recorder start/pause states.
    
    ---
     .../home/room/detail/composer/VoiceMessageHelper.kt         | 6 ++++--
     .../detail/timeline/helper/VoiceMessagePlaybackTracker.kt   | 2 +-
     2 files changed, 5 insertions(+), 3 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    index b6a8dc2cd5..6bde4ada3d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    @@ -132,9 +132,11 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun startOrPausePlayback(id: String, file: File) {
    -        stopPlayback()
    +        val playbackState = playbackTracker.getPlaybackState(id)
    +        mediaPlayer?.stop()
    +        stopPlaybackTicker()
             stopRecordingAmplitudes()
    -        if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +        if (playbackState is VoiceMessagePlaybackTracker.Listener.State.Playing) {
                 playbackTracker.pausePlayback(id)
             } else {
                 startPlayback(id, file)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    index 076c05b9c4..8167ad94af 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    @@ -115,7 +115,7 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             }
         }
     
    -    fun getPercentage(id: String): Float {
    +    private fun getPercentage(id: String): Float {
             return when (val state = states[id]) {
                 is Listener.State.Playing -> state.percentage
                 is Listener.State.Paused  -> state.percentage
    
    From daafddbe7127037bda084327365a035ecd16cdb2 Mon Sep 17 00:00:00 2001
    From: ariskotsomitopoulos 
    Date: Thu, 3 Mar 2022 19:10:40 +0200
    Subject: [PATCH 0064/1430] fix Realm crash
    
    ---
     .../room/timeline/SendingEventsDataSource.kt  | 25 +++++++++----------
     1 file changed, 12 insertions(+), 13 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    index 4c5322d073..1262c09d97 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
    @@ -66,12 +66,9 @@ internal class RealmSendingEventsDataSource(
     
         private fun updateFrozenResults(sendingEvents: RealmList?) {
             // Makes sure to close the previous frozen realm
    -        // TODO find a better way to avoid thread timeline crash:
    -        //  - Make RealmSendingEventsDataSource Singleton
    -        //  - Do not initialize RealmSendingEventsDataSource when we are in a thread timeline while
    -        //    we already have an instance from the main timeline
    -        //  - Close Main timeline before Opening a thread timeline
    -        // frozenSendingTimelineEvents?.realm?.close()
    +        if (frozenSendingTimelineEvents?.isValid == true) {
    +            frozenSendingTimelineEvents?.realm?.close()
    +        }
             frozenSendingTimelineEvents = sendingEvents?.freeze()
         }
     
    @@ -79,13 +76,15 @@ internal class RealmSendingEventsDataSource(
             val builtSendingEvents = mutableListOf()
             uiEchoManager.getInMemorySendingEvents()
                     .addWithUiEcho(builtSendingEvents)
    -        frozenSendingTimelineEvents
    -                ?.filter { timelineEvent ->
    -                    builtSendingEvents.none { it.eventId == timelineEvent.eventId }
    -                }
    -                ?.map {
    -                    timelineEventMapper.map(it)
    -                }?.addWithUiEcho(builtSendingEvents)
    +        if (frozenSendingTimelineEvents?.isValid == true) {
    +            frozenSendingTimelineEvents
    +                    ?.filter { timelineEvent ->
    +                        builtSendingEvents.none { it.eventId == timelineEvent.eventId }
    +                    }
    +                    ?.map {
    +                        timelineEventMapper.map(it)
    +                    }?.addWithUiEcho(builtSendingEvents)
    +        }
     
             return builtSendingEvents
         }
    
    From cb32124fd409a32a89bc1a9458b93c0d2f3fcb29 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 18:28:13 +0000
    Subject: [PATCH 0065/1430] Fix typo in name of action
    
    ---
     .github/workflows/nightly.yml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index d90dafd93b..b93258112e 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -316,7 +316,7 @@ jobs:
             with: 
               distribution: 'adopt'
               java-version: '11'
    -      - uses: action/cache@v2
    +      - uses: actions/cache@v2
             with:
               path: |
                 ~/.gradle/caches
    
    From 9fe04ac5b85fef09a05935ab8efeae18fabe73b2 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 20:26:33 +0000
    Subject: [PATCH 0066/1430] Remove exclusions (for now).
    
    We can merge this PR without and re-add afterwards the only ones we want.
    ---
     coverage.gradle | 27 +--------------------------
     1 file changed, 1 insertion(+), 26 deletions(-)
    
    diff --git a/coverage.gradle b/coverage.gradle
    index 098c67c26f..7cc87baad9 100644
    --- a/coverage.gradle
    +++ b/coverage.gradle
    @@ -1,29 +1,4 @@
    -def excludes = [
    -        // DI graph
    -        '**/*Module.*',
    -        '**/*Module*.*',
    -
    -        // Android composables
    -        '**/*Screen*',
    -        '**/components/*',
    -        '**/*Compose*.*',
    -
    -        // Android framework
    -        '**/*Activity*',
    -        '**/*AndroidService*',
    -        '**/*Application*',
    -
    -        // Generated
    -        '**/*serializer*',
    -        '**/*Serializer*',
    -        "**/*request/*Companion*.*",
    -        '**/*QueriesImpl*',
    -        '**/*Db*',
    -        '**/Select*',
    -
    -        // Tmp until serializationx can ignore generated
    -        '**/Api*',
    -]
    +def excludes = [ ]
     
     def initializeReport(report, projects, classExcludes) {
         projects.each { project -> project.apply plugin: 'jacoco' }
    
    From 2d5b25cfad69631bc3a7d807a3d454ddfa4efb77 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Thu, 3 Mar 2022 20:39:41 +0000
    Subject: [PATCH 0067/1430] Remove the printing of file name to the log as it's
     doubling up information.
    
    ---
     tools/ci/render_test_output.py | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/tools/ci/render_test_output.py b/tools/ci/render_test_output.py
    index 9df3170058..1e7940ce04 100755
    --- a/tools/ci/render_test_output.py
    +++ b/tools/ci/render_test_output.py
    @@ -13,7 +13,6 @@ print("::group::Arguments")
     print(f"{sys.argv}") 
     print("::endgroup::")
     for xmlfile in xmlfiles:
    -    print(f"Handling: {xmlfile}") 
         tree = ET.parse(xmlfile)
         
         root = tree.getroot()
    
    From 2a9e582db5fc0cad3a06b1fca4c9967389c28a5d Mon Sep 17 00:00:00 2001
    From: ClaireG 
    Date: Thu, 3 Mar 2022 22:40:26 +0100
    Subject: [PATCH 0068/1430] Merge pull request #5405 from
     vector-im/cgizard/ISSUE-5402
    
    [Create private room] Picture doesn't not displayed
    ---
     changelog.d/5402.bugfix                       |  1 +
     .../internal/session/content/FileUploader.kt  | 18 +++++----
     .../session/profile/DefaultProfileService.kt  |  8 ++--
     .../room/create/CreateRoomBodyBuilder.kt      | 37 +++++++++----------
     4 files changed, 33 insertions(+), 31 deletions(-)
     create mode 100644 changelog.d/5402.bugfix
    
    diff --git a/changelog.d/5402.bugfix b/changelog.d/5402.bugfix
    new file mode 100644
    index 0000000000..fde9e7e74f
    --- /dev/null
    +++ b/changelog.d/5402.bugfix
    @@ -0,0 +1 @@
    +[Create room] Setting an avatar when creating a room had no effect
    \ No newline at end of file
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
    index b988f2253c..e9cb423893 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
    @@ -30,6 +30,7 @@ import okhttp3.RequestBody
     import okhttp3.RequestBody.Companion.toRequestBody
     import okio.BufferedSink
     import okio.source
    +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.failure.Failure
     import org.matrix.android.sdk.api.failure.MatrixError
    @@ -53,6 +54,7 @@ internal class FileUploader @Inject constructor(
             private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
             private val context: Context,
             private val temporaryFileCreator: TemporaryFileCreator,
    +        private val coroutineDispatchers: MatrixCoroutineDispatchers,
             contentUrlResolver: ContentUrlResolver,
             moshi: Moshi
     ) {
    @@ -146,14 +148,16 @@ internal class FileUploader @Inject constructor(
                     .post(requestBody)
                     .build()
     
    -        return okHttpClient.newCall(request).awaitResponse().use { response ->
    -            if (!response.isSuccessful) {
    -                throw response.toFailure(globalErrorReceiver)
    -            } else {
    -                response.body?.source()?.let {
    -                    responseAdapter.fromJson(it)
    +       return withContext(coroutineDispatchers.io) {
    +             okHttpClient.newCall(request).awaitResponse().use { response ->
    +                if (!response.isSuccessful) {
    +                    throw response.toFailure(globalErrorReceiver)
    +                } else {
    +                    response.body?.source()?.let {
    +                        responseAdapter.fromJson(it)
    +                    }
    +                            ?: throw IOException()
                     }
    -                        ?: throw IOException()
                 }
             }
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    index caf4158657..6f99577ac2 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    @@ -68,11 +68,9 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
         }
     
         override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) {
    -        withContext(coroutineDispatchers.io) {
    -            val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
    -            setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
    -            userStore.updateAvatar(userId, response.contentUri)
    -        }
    +        val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
    +        setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
    +        userStore.updateAvatar(userId, response.contentUri)
         }
     
         override suspend fun getAvatarUrl(userId: String): Optional {
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    index 84261e6ebf..c9914449c3 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    @@ -112,19 +112,18 @@ internal class CreateRoomBodyBuilder @Inject constructor(
         private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? {
             return params.avatarUri?.let { avatarUri ->
                 // First upload the image, ignoring any error
    -            tryOrNull {
    +            tryOrNull("Failed to upload image") {
                     fileUploader.uploadFromUri(
                             uri = avatarUri,
                             filename = UUID.randomUUID().toString(),
                             mimeType = MimeTypes.Jpeg)
                 }
    -                    ?.let { response ->
    -                        Event(
    -                                type = EventType.STATE_ROOM_AVATAR,
    -                                stateKey = "",
    -                                content = mapOf("url" to response.contentUri)
    -                        )
    -                    }
    +        }?.let { response ->
    +            Event(
    +                    type = EventType.STATE_ROOM_AVATAR,
    +                    stateKey = "",
    +                    content = mapOf("url" to response.contentUri)
    +            )
             }
         }
     
    @@ -180,19 +179,19 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                     params.invite3pids.isEmpty() &&
                     params.invitedUserIds.isNotEmpty() &&
                     params.invitedUserIds.let { userIds ->
    -            val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
    +                    val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
     
    -            userIds.all { userId ->
    -                keys.map[userId].let { deviceMap ->
    -                    if (deviceMap.isNullOrEmpty()) {
    -                        // A user has no device, so do not enable encryption
    -                        false
    -                    } else {
    -                        // Check that every user's device have at least one key
    -                        deviceMap.values.all { !it.keys.isNullOrEmpty() }
    +                    userIds.all { userId ->
    +                        keys.map[userId].let { deviceMap ->
    +                            if (deviceMap.isNullOrEmpty()) {
    +                                // A user has no device, so do not enable encryption
    +                                false
    +                            } else {
    +                                // Check that every user's device have at least one key
    +                                deviceMap.values.all { !it.keys.isNullOrEmpty() }
    +                            }
    +                        }
                         }
                     }
    -            }
    -        }
         }
     }
    
    From 5168d715ceb89ec62a31ea0504232952dd6c7c57 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 16:21:28 +0300
    Subject: [PATCH 0069/1430] Support scrolling playback on recorded audio before
     sending.
    
    ---
     .../home/room/detail/TimelineFragment.kt      | 20 ++++++++++++----
     .../detail/composer/MessageComposerAction.kt  |  4 ++--
     .../composer/MessageComposerViewModel.kt      |  8 ++-----
     .../detail/composer/VoiceMessageHelper.kt     |  6 ++---
     .../voice/VoiceMessageRecorderView.kt         | 17 +++++++++++++-
     .../composer/voice/VoiceMessageViews.kt       | 23 ++++++++++++++++---
     .../timeline/TimelineEventController.kt       |  4 ++--
     .../timeline/factory/MessageItemFactory.kt    |  6 +++--
     8 files changed, 65 insertions(+), 23 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    index d019cb1777..a0e8ddce3d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    @@ -786,6 +786,18 @@ class TimelineFragment @Inject constructor(
                     updateRecordingUiState(RecordingUiState.Draft)
                 }
     
    +            override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(VoiceMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
                 private fun updateRecordingUiState(state: RecordingUiState) {
                     messageComposerViewModel.handle(
                             MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
    @@ -2051,12 +2063,12 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    -    override fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    -        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, messageAudioContent, percentage))
    +    override fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, duration, percentage))
         }
     
    -    override fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float) {
    -        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, messageAudioContent, percentage))
    +    override fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, duration, percentage))
         }
     
         private fun onShareActionClicked(action: EventSharedAction.Share) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    index daa5631d84..091e9f7869 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    @@ -40,6 +40,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
         data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
         object PlayOrPauseRecordingPlayback : MessageComposerAction()
         data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
    -    data class VoiceWaveformTouchedUp(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
    -    data class VoiceWaveformMovedTo(val eventId: String, val messageAudioContent: MessageAudioContent, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index ccb51d3796..fba3b8b5d3 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    @@ -864,15 +864,11 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    -        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    -        val toMillisecond = (action.percentage * duration).toInt()
    -        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
         }
     
         private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    -        val duration = (action.messageAudioContent.audioInfo?.duration ?: 0)
    -        val toMillisecond = (action.percentage * duration).toInt()
    -        voiceMessageHelper.movePlaybackTo(action.eventId, toMillisecond, duration)
    +        voiceMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
         }
     
         private fun handleEntersBackground(composerText: String) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    index 6bde4ada3d..c5d8b7a5c1 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    @@ -171,13 +171,13 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun stopPlayback() {
    -        playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
    +        playbackTracker.pausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
             mediaPlayer?.stop()
             stopPlaybackTicker()
         }
     
    -    fun movePlaybackTo(id: String, toMillisecond: Int, totalDuration: Int) {
    -        val percentage = toMillisecond.toFloat() / totalDuration
    +    fun movePlaybackTo(id: String, percentage: Float, totalDuration: Int) {
    +        val toMillisecond = (totalDuration * percentage).toInt()
             playbackTracker.updateCurrentPlaybackTime(id, toMillisecond, percentage)
     
             stopPlayback()
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    index 9a643796a9..87a2630f2a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    @@ -53,6 +53,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
             fun onDeleteVoiceMessage()
             fun onRecordingLimitReached()
             fun onRecordingWaveformClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int)
    +        fun onVoiceWaveformMoved(percentage: Float, duration: Int)
         }
     
         @Inject lateinit var clock: Clock
    @@ -65,6 +67,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         private var recordingTicker: CountUpTimer? = null
         private var lastKnownState: RecordingUiState? = null
         private var dragState: DraggingState = DraggingState.Ignored
    +    private var recordingDuration: Long = 0
     
         init {
             inflate(this.context, R.layout.view_voice_message_recorder, this)
    @@ -95,7 +98,6 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
                 override fun onWaveformClicked() {
                     when (lastKnownState) {
    -                    RecordingUiState.Draft  -> callback.onVoicePlaybackButtonClicked()
                         is RecordingUiState.Recording,
                         is RecordingUiState.Locked -> callback.onRecordingWaveformClicked()
                     }
    @@ -105,6 +107,18 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) {
                     onDrag(dragState, newDragState = nextDragStateCreator(dragState))
                 }
    +
    +            override fun onVoiceWaveformTouchedUp(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformTouchedUp(percentage, recordingDuration.toInt())
    +                }
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformMoved(percentage, recordingDuration.toInt())
    +                }
    +            }
             })
         }
     
    @@ -203,6 +217,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         }
     
         private fun stopRecordingTicker() {
    +        recordingDuration = recordingTicker?.elapsedTime() ?: 0
             recordingTicker?.stop()
             recordingTicker = null
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    index 8adecaad6e..f3b1fc918d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    @@ -60,8 +60,21 @@ class VoiceMessageViews(
                 actions.onDeleteVoiceMessage()
             }
     
    -        views.voicePlaybackWaveform.setOnClickListener {
    -            actions.onWaveformClicked()
    +        views.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +            when (motionEvent.action) {
    +                MotionEvent.ACTION_DOWN -> {
    +                    actions.onWaveformClicked()
    +                }
    +                MotionEvent.ACTION_UP   -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformTouchedUp(percentage)
    +                }
    +                MotionEvent.ACTION_MOVE -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformMoved(percentage)
    +                }
    +            }
    +            true
             }
     
             views.voicePlaybackControlButton.setOnClickListener {
    @@ -70,6 +83,8 @@ class VoiceMessageViews(
             observeMicButton(actions)
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = motionEvent.x / view.width
    +
         @SuppressLint("ClickableViewAccessibility")
         private fun observeMicButton(actions: Actions) {
             val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
    @@ -332,7 +347,7 @@ class VoiceMessageViews(
     
         fun renderRecordingWaveform(amplitudeList: Array) {
             views.voicePlaybackWaveform.doOnLayout { waveFormView ->
    -            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_secondary)
    +            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary)
                 amplitudeList.iterator().forEach {
                     (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor))
                 }
    @@ -355,5 +370,7 @@ class VoiceMessageViews(
             fun onDeleteVoiceMessage()
             fun onWaveformClicked()
             fun onVoicePlaybackButtonClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float)
    +        fun onVoiceWaveformMoved(percentage: Float)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    index 3965afdbaa..9c469dfead 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    @@ -138,8 +138,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    -        fun onVoiceWaveformTouchedUp(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    -        fun onVoiceWaveformMovedTo(eventId: String, messageAudioContent: MessageAudioContent, percentage: Float)
    +        fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float)
         }
     
         interface ReactionPillCallback {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index 8b0b43009d..9116de92dd 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -359,11 +359,13 @@ class MessageItemFactory @Inject constructor(
     
             val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
                 override fun onWaveformTouchedUp(percentage: Float) {
    -                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, messageContent, percentage)
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, duration, percentage)
                 }
     
                 override fun onWaveformMovedTo(percentage: Float) {
    -                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, messageContent, percentage)
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, duration, percentage)
                 }
             }
     
    
    From aae75ce52fa1541b32f54ee96a3442b65a92844a Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 16:54:56 +0300
    Subject: [PATCH 0070/1430] Always stop all voice actions and media player if
     app enters to the background.
    
    ---
     .../home/room/detail/composer/MessageComposerViewModel.kt  | 7 +++++--
     1 file changed, 5 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index fba3b8b5d3..b71398c8a2 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    @@ -872,11 +872,14 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleEntersBackground(composerText: String) {
    +        // Always stop all voice actions. It may be playing in timeline or active recording
    +        val playingAudioContent = voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)
    +        voiceMessageHelper.clearTracker()
    +        
             val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
             if (isVoiceRecording) {
    -            voiceMessageHelper.clearTracker()
                 viewModelScope.launch {
    -                voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)?.toContentAttachmentData()?.let { voiceDraft ->
    +                playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
                         val content = voiceDraft.toJsonString()
                         room.saveDraft(UserDraft.Voice(content))
                         setState { copy(sendMode = SendMode.Voice(content)) }
    
    From 601f10a6fb7e62f43e9d1ec8dbcf898bcbf50b78 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:16:09 +0300
    Subject: [PATCH 0071/1430] Support ltr and rtl flow of the recording waveform.
    
    ---
     .../java/im/vector/app/features/voice/AudioWaveformView.kt   | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    index 768635b2f7..7cdb1d51d5 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -164,9 +164,10 @@ class AudioWaveformView @JvmOverloads constructor(
     
         private fun drawBars(canvas: Canvas) {
             var currentX = horizontalPadding
    -        visibleBarHeights.forEach {
    +        val flowableBarHeights = if (flow == Flow.LTR) visibleBarHeights else visibleBarHeights.reversed()
    +
    +        flowableBarHeights.forEach {
                 barPaint.color = it.color
    -            // TODO. Support flow
                 when (alignment) {
                     Alignment.BOTTOM -> {
                         val startY = height - verticalPadding
    
    From e09b123a9191e1d0aaea845657675ce05f982ca1 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:21:00 +0300
    Subject: [PATCH 0072/1430] Changelog added.
    
    ---
     changelog.d/5426.feature | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 changelog.d/5426.feature
    
    diff --git a/changelog.d/5426.feature b/changelog.d/5426.feature
    new file mode 100644
    index 0000000000..2dee22f07a
    --- /dev/null
    +++ b/changelog.d/5426.feature
    @@ -0,0 +1 @@
    +Allow scrolling position of Voice Message playback
    \ No newline at end of file
    
    From 4cb432e49704e2ccd5132fafcfad851754e40135 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Fri, 4 Mar 2022 17:47:34 +0300
    Subject: [PATCH 0073/1430] Do not allow to flow RTL after summarized, playback
     time always flows LTR.
    
    ---
     .../main/java/im/vector/app/features/voice/AudioWaveformView.kt  | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    index 7cdb1d51d5..32f30fe458 100644
    --- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
    @@ -129,6 +129,7 @@ class AudioWaveformView @JvmOverloads constructor(
         }
     
         private fun List.summarize(target: Int): List {
    +        flow = Flow.LTR
             val result = mutableListOf()
             if (size <= target) {
                 result.addAll(this)
    
    From 662f72fde807b5be006cd00c9a0643448d03267c Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Fri, 4 Mar 2022 15:01:24 +0000
    Subject: [PATCH 0074/1430] Remove unneeded code, retaining a comment for how
     to exclude certain projects
    
    ---
     coverage.gradle | 23 +++--------------------
     1 file changed, 3 insertions(+), 20 deletions(-)
    
    diff --git a/coverage.gradle b/coverage.gradle
    index 7cc87baad9..545b93f93b 100644
    --- a/coverage.gradle
    +++ b/coverage.gradle
    @@ -43,29 +43,12 @@ def collectProjects(predicate) {
         return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
     }
     
    -//task unitCodeCoverageReport(type: JacocoReport) {
    -//    outputs.upToDateWhen { false }
    -//    rootProject.apply plugin: 'jacoco'
    -//    def excludedProjects = [
    -//            'olm-stub',
    -//            'test-harness'
    -//    ]
    -//    def projects = collectProjects { !excludedProjects.contains(it.name) }
    -//    dependsOn { ["app:assembleDebug"] + projects*.test }
    -//    initializeReport(it, projects, excludes)
    -//}
    -//
    -//task harnessCodeCoverageReport(type: JacocoReport) {
    -//    outputs.upToDateWhen { false }
    -//    rootProject.apply plugin: 'jacoco'
    -//    def projects = collectProjects { true }
    -//    dependsOn { ["app:assembleDebug", project(":test-harness").test] }
    -//    initializeReport(it, projects, excludes)
    -//}
    -
     task allCodeCoverageReport(type: JacocoReport) {
         outputs.upToDateWhen { false }
         rootProject.apply plugin: 'jacoco'
    +    // to limit projects in a specific report, add
    +    // def excludedProjects = [ ... ]
    +    // def projects = collectProjects { !excludedProjects.contains(it.name) }
         def projects = collectProjects { true }
         dependsOn { projects*.test }
         initializeReport(it, projects, excludes)
    
    From 2dedad1cf237d3d314e1c38bbe2c325997541a95 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Fri, 4 Mar 2022 15:03:12 +0000
    Subject: [PATCH 0075/1430] Address review points from adam
    
    ---
     coverage.gradle | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/coverage.gradle b/coverage.gradle
    index 545b93f93b..96881dfff2 100644
    --- a/coverage.gradle
    +++ b/coverage.gradle
    @@ -22,7 +22,7 @@ def initializeReport(report, projects, classExcludes) {
                         androidSourceDirs.add("${project.projectDir}/src/main/java")
                         break
                     case { project.plugins.hasPlugin("com.android.library") }:
    -                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/release")
    +                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
                         androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
                         androidSourceDirs.add("${project.projectDir}/src/main/java")
                         break
    
    From 96168929fff42d8d968e0347bb381e99418dc5f1 Mon Sep 17 00:00:00 2001
    From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
    Date: Fri, 4 Mar 2022 15:15:52 +0000
    Subject: [PATCH 0076/1430] Tweak upload/download of codecov xml file
    
    ---
     .github/workflows/nightly.yml | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
    index b93258112e..37797129bb 100644
    --- a/.github/workflows/nightly.yml
    +++ b/.github/workflows/nightly.yml
    @@ -331,7 +331,7 @@ jobs:
             with:
               name: codecov-xml
               path: |
    -            build/jacoco/*.xml
    +            build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
     
       sonarqube:
         runs-on: macos-latest
    @@ -354,7 +354,8 @@ jobs:
                 ${{ runner.os }}-gradle-
           - uses: actions/download-artifact@v3
             with:
    -          artifact: codecov-xml # will restore to build/ as a local run would do
    +          name: codecov-xml # will restore to build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml by default
    +      - run: ls -R build/reports # temporary check to see if the report is actually downloading correctly.
           - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
             env:
               ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
    
    From bcdf004082ad15961c929280998b485c4409e221 Mon Sep 17 00:00:00 2001
    From: Ahmed Radhouane Belkilani
     <73594434+ahmed-radhouane@users.noreply.github.com>
    Date: Fri, 4 Mar 2022 16:38:53 +0100
    Subject: [PATCH 0077/1430] Update the top bar in a room (#5213)
    
    Remove typing message notification from room toolbar.
    
    Signed-off-by: Ahmed Radhouane Belkilani 
    ---
     changelog.d/4642.bugfix                       |  1 +
     .../main/res/values/style_action_button.xml   |  8 +++
     .../src/main/res/values/theme_dark.xml        |  3 ++
     .../src/main/res/values/theme_light.xml       |  3 ++
     .../home/room/detail/TimelineFragment.kt      | 21 +-------
     .../res/layout/view_room_detail_toolbar.xml   | 52 ++++++-------------
     6 files changed, 34 insertions(+), 54 deletions(-)
     create mode 100644 changelog.d/4642.bugfix
     create mode 100644 library/ui-styles/src/main/res/values/style_action_button.xml
    
    diff --git a/changelog.d/4642.bugfix b/changelog.d/4642.bugfix
    new file mode 100644
    index 0000000000..2a5ea97196
    --- /dev/null
    +++ b/changelog.d/4642.bugfix
    @@ -0,0 +1 @@
    +Update the top bar in a room: remove topic and typing information
    \ No newline at end of file
    diff --git a/library/ui-styles/src/main/res/values/style_action_button.xml b/library/ui-styles/src/main/res/values/style_action_button.xml
    new file mode 100644
    index 0000000000..0a3c73622f
    --- /dev/null
    +++ b/library/ui-styles/src/main/res/values/style_action_button.xml
    @@ -0,0 +1,8 @@
    +
    +
    +    
    +
    \ No newline at end of file
    diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
    index 100a07f41d..31e64184bc 100644
    --- a/library/ui-styles/src/main/res/values/theme_dark.xml
    +++ b/library/ui-styles/src/main/res/values/theme_dark.xml
    @@ -141,6 +141,9 @@
             @style/Widget.Vector.Keyword
     
             @color/vctr_toast_background_dark
    +
    +        @style/Widget.Vector.ActionButton
    +
         
     
         
     
         
     
         
     
         
     
         
    -
    -    
    -
    -    
    -
    -    
    -
    -
    \ No newline at end of file
    diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
    index 6610c0f45d..461dacdf68 100644
    --- a/library/ui-styles/src/main/res/values/colors.xml
    +++ b/library/ui-styles/src/main/res/values/colors.xml
    @@ -9,10 +9,6 @@
         #2f9edb
         ?colorError
     
    -    
    -    #14368BD6
    -    @color/palette_azure
    -
         
         @color/palette_azure
         @color/palette_melon
    @@ -22,7 +18,6 @@
         #99000000
         #27303A
     
    -    #FF61708B
         #1E61708B
     
         
    @@ -83,16 +78,6 @@
         #BF000000
         #BF000000
     
    -    
    -    #FFFFFFFF
    -    #FF22262E
    -    #FF090A0C
    -
    -    
    -    #FFE9EDF1
    -    #FF22262E
    -    #FF090A0C
    -
         
         #EBEFF5
         #27303A
    @@ -107,9 +92,7 @@
         #AAAAAAAA
         #55555555
     
    -    
         #EEEEEE
    -    #61708B
     
         
         #FFF3F8FD
    diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
    index db42cfa12c..8307915474 100644
    --- a/library/ui-styles/src/main/res/values/dimens.xml
    +++ b/library/ui-styles/src/main/res/values/dimens.xml
    @@ -9,20 +9,11 @@
         32dp
     
         50dp
    -    16dp
    -    196dp
    -    44dp
    -    72dp
         16dp
     
         32dp
     
    -    40dp
    -    60dp
    -
    -    4dp
         8dp
    -    8dp
     
         0.75
     
    diff --git a/library/ui-styles/src/main/res/values/palette.xml b/library/ui-styles/src/main/res/values/palette.xml
    index e37fd8a7c6..625745f709 100644
    --- a/library/ui-styles/src/main/res/values/palette.xml
    +++ b/library/ui-styles/src/main/res/values/palette.xml
    @@ -1,5 +1,7 @@
     
    -
    +
    +
    +    
     
         
    @@ -15,6 +17,7 @@
         #0DBD8B
         #FFFFFF
         #FF5B55
    +    
         #7E69FF
         #2DC2C5
         #5C56F5
    @@ -27,6 +30,7 @@
         #8D97A5
         #737D8C
         #17191C
    +    
         #F4F9FD
     
         
    diff --git a/library/ui-styles/src/main/res/values/palette_mobile.xml b/library/ui-styles/src/main/res/values/palette_mobile.xml
    index c22b9705c7..ec2f1d0814 100644
    --- a/library/ui-styles/src/main/res/values/palette_mobile.xml
    +++ b/library/ui-styles/src/main/res/values/palette_mobile.xml
    @@ -35,7 +35,6 @@
         
         @color/palette_gray_25
         @color/palette_black_950
    -    @color/palette_black_950
     
         @color/palette_white
         @color/palette_black_800
    diff --git a/library/ui-styles/src/main/res/values/styles_attachments.xml b/library/ui-styles/src/main/res/values/styles_attachments.xml
    deleted file mode 100644
    index 18c2e3f95f..0000000000
    --- a/library/ui-styles/src/main/res/values/styles_attachments.xml
    +++ /dev/null
    @@ -1,18 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -
    \ No newline at end of file
    diff --git a/library/ui-styles/src/main/res/values/styles_buttons.xml b/library/ui-styles/src/main/res/values/styles_buttons.xml
    index d09d0a399d..004aca5aaa 100644
    --- a/library/ui-styles/src/main/res/values/styles_buttons.xml
    +++ b/library/ui-styles/src/main/res/values/styles_buttons.xml
    @@ -33,24 +33,6 @@
             
         
     
    -    
    -
    -    
    -
    -    
    -
         
     
    -    
    -
     
    \ No newline at end of file
    diff --git a/library/ui-styles/src/main/res/values/styles_dial_pad.xml b/library/ui-styles/src/main/res/values/styles_dial_pad.xml
    index 34e128c56d..77dc0b3081 100644
    --- a/library/ui-styles/src/main/res/values/styles_dial_pad.xml
    +++ b/library/ui-styles/src/main/res/values/styles_dial_pad.xml
    @@ -1,5 +1,8 @@
     
    -
    +
    +
    +    
     
         
     
    -    
    -
         
    diff --git a/library/ui-styles/src/main/res/values/theme_black.xml b/library/ui-styles/src/main/res/values/theme_black.xml
    index 44d4206d43..6e5ce80c19 100644
    --- a/library/ui-styles/src/main/res/values/theme_black.xml
    +++ b/library/ui-styles/src/main/res/values/theme_black.xml
    @@ -11,8 +11,6 @@
             @color/vctr_fab_label_stroke_black
             @color/vctr_fab_label_color_black
             @color/vctr_touch_guard_bg_black
    -        @color/vctr_attachment_selector_background_black
    -        @color/vctr_attachment_selector_border_black
             @color/vctr_room_active_widgets_banner_bg_black
             @color/vctr_room_active_widgets_banner_text_black
             @color/vctr_reaction_background_off_black
    diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
    index 607f008453..23cdaea5e2 100644
    --- a/library/ui-styles/src/main/res/values/theme_dark.xml
    +++ b/library/ui-styles/src/main/res/values/theme_dark.xml
    @@ -21,8 +21,6 @@
             @color/vctr_fab_label_color_dark
             @color/vctr_touch_guard_bg_dark
             @color/vctr_keys_backup_banner_accent_color_dark
    -        @color/vctr_attachment_selector_background_dark
    -        @color/vctr_attachment_selector_border_dark
             @color/vctr_room_active_widgets_banner_bg_dark
             @color/vctr_room_active_widgets_banner_text_dark
             @color/vctr_reaction_background_off_dark
    diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
    index efc18b9f32..6189a1f399 100644
    --- a/library/ui-styles/src/main/res/values/theme_light.xml
    +++ b/library/ui-styles/src/main/res/values/theme_light.xml
    @@ -21,8 +21,6 @@
             @color/vctr_fab_label_color_light
             @color/vctr_touch_guard_bg_light
             @color/vctr_keys_backup_banner_accent_color_light
    -        @color/vctr_attachment_selector_background_light
    -        @color/vctr_attachment_selector_border_light
             @color/vctr_room_active_widgets_banner_bg_light
             @color/vctr_room_active_widgets_banner_text_light
             @color/vctr_reaction_background_off_light
    diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
    index 3e301eebb9..ff623760e0 100644
    --- a/matrix-sdk-android/build.gradle
    +++ b/matrix-sdk-android/build.gradle
    @@ -34,9 +34,8 @@ android {
             buildConfigField "String", "SDK_VERSION", "\"1.4.4\""
     
             buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
    -        resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
    -        resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
    -        resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
    +        buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
    +        buildConfigField "String", "GIT_SDK_REVISION_DATE", "\"${gitRevisionDate()}\""
     
             defaultConfig {
                 consumerProguardFiles 'proguard-rules.pro'
    diff --git a/vector/lint.xml b/vector/lint.xml
    index 6bdd2ae585..e219ac1eed 100644
    --- a/vector/lint.xml
    +++ b/vector/lint.xml
    @@ -20,24 +20,9 @@
             
         
     
    -    
         
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    -        
    +        
    +        
     
             
             
    diff --git a/vector/src/main/res/anim/anim_slide_in_bottom.xml b/vector/src/main/res/anim/anim_slide_in_bottom.xml
    deleted file mode 100644
    index d696a09e13..0000000000
    --- a/vector/src/main/res/anim/anim_slide_in_bottom.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -
    -    
    -
    diff --git a/vector/src/main/res/anim/anim_slide_nothing.xml b/vector/src/main/res/anim/anim_slide_nothing.xml
    deleted file mode 100644
    index c0bf1b82bc..0000000000
    --- a/vector/src/main/res/anim/anim_slide_nothing.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -
    -    
    -
    diff --git a/vector/src/main/res/anim/anim_slide_out_bottom.xml b/vector/src/main/res/anim/anim_slide_out_bottom.xml
    deleted file mode 100644
    index e9fd4e9a7f..0000000000
    --- a/vector/src/main/res/anim/anim_slide_out_bottom.xml
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -
    -
    -    
    -    
    -
    -
    diff --git a/vector/src/main/res/anim/enter_from_left.xml b/vector/src/main/res/anim/enter_from_left.xml
    deleted file mode 100644
    index e20f4f589a..0000000000
    --- a/vector/src/main/res/anim/enter_from_left.xml
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/anim/enter_from_right.xml b/vector/src/main/res/anim/enter_from_right.xml
    deleted file mode 100644
    index e749a77191..0000000000
    --- a/vector/src/main/res/anim/enter_from_right.xml
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/anim/exit_to_left.xml b/vector/src/main/res/anim/exit_to_left.xml
    deleted file mode 100644
    index ef67949730..0000000000
    --- a/vector/src/main/res/anim/exit_to_left.xml
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/anim/exit_to_right.xml b/vector/src/main/res/anim/exit_to_right.xml
    deleted file mode 100644
    index 9c7dca9435..0000000000
    --- a/vector/src/main/res/anim/exit_to_right.xml
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/anim/unread_marker_anim.xml b/vector/src/main/res/anim/unread_marker_anim.xml
    deleted file mode 100644
    index 9e61c80c9d..0000000000
    --- a/vector/src/main/res/anim/unread_marker_anim.xml
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -
    -
    -    
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable-xxhdpi/ic_material_call_end_grey.png b/vector/src/main/res/drawable-xxhdpi/ic_material_call_end_grey.png
    deleted file mode 100644
    index 638811519461737521d210512b68d47202fe356b..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 4424
    zcmbVO^;;8;^WQ)i4bmx~C?QT@AmJ$K4r!#Md(>z_N~EQvLt-G^jT4m|ASEz17$6~C
    zV<-wA-haaP`?}|K_dKt=`{91L=kBe(t{OE3I|Tp$pw>`VHoS>x|B;OJ#&4;vbpiks
    zun;3Ne?z-K9+IyLht98`|?TCC|?IBNE1n>;e0wHo&2Ju
    zub?r_#`ou@=jKi!PKB2})p>Em%F$!%3O7pQ?)E8*pT=rj!M-1k_wjJ;
    zCb{BC_ul@sYufoQmn^wjvG&229gDkHBLTlp6dD?Kch~XX3GSTX)S1hmSJvO0&rj#W
    z|FBp6?AWb-WieH2D>hp3betFhi^m7J(BByiJ_~FcPlh(%N`d;tK
    z;K6bk0ljqK+6Ii5`(I1#*c^5Xe`{K>0y!+(Wd2acHx!@A)7S|;!op{?9``54uaA$F>P
    z$z_PXUv#m_)gO2qxO_;A-EGnl+zG8UGlgrnP)!y@__B~|5SX3^uU+?2cqey7@clYQnVOO92s%4GMaHO2)p9Oi^@2vZWJ=LxescXhg}
    zsB-IC(dBJF^8IG_D(1m`_~o`#xi%N$sY)y3HxuvFUJp35PJQ_1N8~LRSb#@S*S03zf1}HdBn$H@!
    zpQC~&)`l5l9h$ShX85H$CsCPbnR|syV&9|wiF+&V*o<0U8Ei%&4kF%l;i^L&50-kv
    z`Q4r65)6xO1v7~NL@XoIhs_s)+6S9Xj^+H(k8%+x}+lu>gS5s
    z@OMj`7gH|^1@`oZxIH!1uLFcJCYTXC+It+uDTu&kZEW|qu^Vn$seD8
    zz(@ZXj}GaCXbOLxjMF$@?*jRyYwKnym@7(1FXvujB~xGRqjNUc&wS{I3p9H>W7_xJ-lU%INPUy_^qiYx6qLE;
    zxZ?VcExt_w;(>vsfbQ~CH9Ksz5Rk{lj5Q=)TDK(OidcijXm8}wGTh&^t);w~V|Y)f
    zUtH{Oh78sGkbS)T#oCuC(pLYn)?8cGzuygVd&rd+pNzC6)<|@wRM)o6g~&>Rqi5ru
    zgi-w!KPIBMo~VNz)~VFhF>q`T+nY>{W*~(y*69TmW$b^5HHvxZx3mqhkGp}wJDXkG
    zz^Dr}e5UrOdj#yRx8;3@(u94rpwH{{_SoxCl54aU8=Yq~h9%J=?I-i1JSO}?Bj`lR
    zX|k%@*COA*F7$#)(1oLlnGWaY&&IrS*B`VoEN0#(Nm{q;X74deoa{rl`Q)33u;ETh
    zhVeP|^qQtl1{IZEvq&gfTd2@?6wRkMz#|oiO15MU_*)+0OS*QUdfutrQfTFl5b5aG
    zVM+m2j@$VZj
    zZI#o_UU(-#P<1h6Mp{X4;BjPlVP|rGbfQ8E;ed
    z>uANWBpixTzqo?Xyfp4R2;-|X=_>v1Wu={KVVzcOH~6Gwn&opjojc*3KeWw6cf@%g
    z%-YZ*hSa*ZRmz&HxY7}}6F}uCZAza7Wq>NC&l_$NIh&_0TDxMhq{i=%BwFSxnkrdr@1q~58o#Y3bk@bqRINNxGc#>fXHaS%@bTro
    zzG&UFkAKOn^P{xnK;`F~&gT-q?-o{{mpffKDa6`7nBo{2H&3d8xr)f*XDXTc$*%=g
    zC|meu-8(0;*X12*;q)&tF7pt+rOEqTCWoi^be#6pJ+~lvc-Cpbkbt~YoF0oh>Y~Nq
    zvm6ykTfKF`-
    z^mb003mSWZW~7>SNe0{tIUWooq5bq>L640~_lU@eZDKC7=Ycmr)%*4_ZQ!@GVnp(Z
    zqn}waMXMy(c?PGSN2cVM1*%hh9%V_DR*TH^@ZToC;~vTjp469*j92wBiry{i7YL=JIm0!35m5v4pICU-liujhJIy3
    z>ABCCAWc?$S5weGYasm11lzvOj9(`Z8zC3B8Oy9KzhvjstcR%Y_~g>B~_cjP;f
    zqru>BZZ06G89hZJvUZ5~*{wLr0&(Ve=O~3tS4gr?WIqF)?PeK4^7FPCRRk!>hWkr{
    z`fvm1HQRRa>#LpUGg<;wEh+*iX}lJm#N<ob!eTa-3g&d}C?gs#X
    z@BK$$P=!4FMg;k5=%|3!$mnRnByRxizit}MHIx;NZr)RDXuOdl>(KF`c4d|k{B!GF
    zt=L8}dhvZTMmyC~4b$oc1)%{ks;~sHsDy!ZkaEIR71<+Uf$0zVW_Fc2jvJ3!9#%5t
    zO^;_`0{ZYnmzw!?JuQCHewX1m5uSsC0>aI*N%TGZZ!17xMmG>Ugt|H_uSiN0a6rstYA|j5A%hRvyv4sM^v&4lcKPu
    zv&xaEJtJI`8K>!J^|6f4o#N5i~G<&ykNv9xKq_mR#>X}7UB
    z3Fr@%E%-wPCpd$hQ%3{3aTT8`J4J0pD!@EO7|KH+(7GS2kx)p_WeG5Wu!kbOV-Px`
    zh!TgSn*WwmIDWPb#p$-a^S5Ic;a~PqNDZ2qr$h$0ag02Fj+#g|$7#l@QNUzHcquXU
    z?P)|h6ZFh{bb?>2gjes_ynA*wLGLBPBKS2<_$UsoNO2azoI~!>P%7D1mO`uUPTe>E
    z2{1Xx&-#GAsHL97eMd^6#_eZ#JHWL2a#J|6=aZ6IA_A?{N_6otg}Akw^w}&?QXql0
    z1luMO7|}AS)XI^QzBvGNO}vDwVdmXvCND3qc|(yjM(*)fr1b%W)ZIRz00JjWhv`Sf
    zH6^Y8(O)?D{Uul<-Png;=wBG&jx7zWuqOg7fOyjN;$VqSqlh0hRj713)h4U4^V%k;
    zBm+b_?!jrN(WZ%IX^lL}8Rs=OR#>xmUh;<)`cXZb<_(b;~aI>?*DVGhdy{93K6|5@%KO|6$MBKCls@
    zSHY_~10FLJ3F$~cg9!?b+i$IJsR}^v|4ROG``}jy@BWbj@D!>t!LWZHlNqij3!X9*
    z?0Vs4n$Fj7yT8Lj7+WmqMpD1+71S*o!z5kc$*)ZYp5=YKn+6v=qWB#lIDU21YdO5~
    z5FXncrqs&Td9#MQbO`znEw2M38OGR(yyg`^)o<%}*0ga?ZCeL2+^kyT8gsTJt&+&r
    z6Up@yCGlL7;^13+Ro4W(h
    MP|;Pce`Xu?KMbFL-2eap
    
    diff --git a/vector/src/main/res/drawable-xxhdpi/ic_material_done_white.png b/vector/src/main/res/drawable-xxhdpi/ic_material_done_white.png
    deleted file mode 100755
    index 0ebb55559b55f99df3270796763745a71f113078..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 255
    zcmV=}0*5fKp)HP1G&I6Iqc
    zjKz5rgEVtukiIy2Qo_-K;J|?c2M!!K@i?}(lWik$uJ;4aeo|e|;mI*SsV?U@bDX5g
    zSJ{Te;g>S0!OODIPipkgGo)TTcRZxVWAGnIFqKOr(UM3=(~BhBSvit;g=R?Vjh>M7
    zAB+M?!6}eboF615Cqq(m7NiKA4^kA)n-qydL_|cirW>R3em?os7vTT^002ovPDHLk
    FV1hF8Y9jyu
    
    diff --git a/vector/src/main/res/drawable-xxhdpi/incoming_call_notification_transparent.png b/vector/src/main/res/drawable-xxhdpi/incoming_call_notification_transparent.png
    deleted file mode 100755
    index b11532ef5a34edaf915afa9f64be727aa763d796..0000000000000000000000000000000000000000
    GIT binary patch
    literal 0
    HcmV?d00001
    
    literal 684
    zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$a>cu_bxCyDx`7I;J!
    zGca%qgD@k*tT_@uLG}_)Usv`A9L$_1f@*Sm+Zh;`EInNuLn>~)y>l?|u!DfB<4MVh
    ze2oGujT873Z@TME(G*{N_pkL`#sCi6^B$W%76J`{f|w(s)GsQw<=ByArcQTKst0xI`x+`cV*&Wu+R
    z!s^&UOV@1V7g|38p00i_>zopr0B$`3wEzGB
    
    diff --git a/vector/src/main/res/drawable/bg_attachment_type_selector.xml b/vector/src/main/res/drawable/bg_attachment_type_selector.xml
    deleted file mode 100644
    index 2cbad6a855..0000000000
    --- a/vector/src/main/res/drawable/bg_attachment_type_selector.xml
    +++ /dev/null
    @@ -1,23 +0,0 @@
    -
    -
    -    
    -        
    -            
    -            
    -        
    -    
    -
    -    
    -        
    -            
    -            
    -
    -        
    -
    -    
    -
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/bg_button_secondary.xml b/vector/src/main/res/drawable/bg_button_secondary.xml
    deleted file mode 100644
    index 65db98b8d7..0000000000
    --- a/vector/src/main/res/drawable/bg_button_secondary.xml
    +++ /dev/null
    @@ -1,24 +0,0 @@
    -
    -
    -
    -    
    -        
    -            
    -            
    -        
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/bg_group_item.xml b/vector/src/main/res/drawable/bg_group_item.xml
    deleted file mode 100644
    index 88d93ddee3..0000000000
    --- a/vector/src/main/res/drawable/bg_group_item.xml
    +++ /dev/null
    @@ -1,16 +0,0 @@
    -
    -
    -
    -    
    -        
    -            
    -            
    -        
    -    
    -    
    -        
    -            
    -        
    -    
    -
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/bg_tombstone_predecessor.xml b/vector/src/main/res/drawable/bg_tombstone_predecessor.xml
    deleted file mode 100644
    index 4b7a8d2d4f..0000000000
    --- a/vector/src/main/res/drawable/bg_tombstone_predecessor.xml
    +++ /dev/null
    @@ -1,21 +0,0 @@
    -
    -
    -    
    -        
    -            
    -        
    -    
    -
    -    
    -        
    -            
    -            
    -        
    -    
    -
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/call_header_transparent_bg.xml b/vector/src/main/res/drawable/call_header_transparent_bg.xml
    deleted file mode 100644
    index 17408c8b8f..0000000000
    --- a/vector/src/main/res/drawable/call_header_transparent_bg.xml
    +++ /dev/null
    @@ -1,8 +0,0 @@
    -
    -
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/ic_attachment_audio_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_audio_white_24dp.xml
    deleted file mode 100644
    index 3de9a237e8..0000000000
    --- a/vector/src/main/res/drawable/ic_attachment_audio_white_24dp.xml
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -
    -
    -    
    -
    diff --git a/vector/src/main/res/drawable/ic_bell.xml b/vector/src/main/res/drawable/ic_bell.xml
    deleted file mode 100644
    index cb648585ce..0000000000
    --- a/vector/src/main/res/drawable/ic_bell.xml
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_book.xml b/vector/src/main/res/drawable/ic_book.xml
    deleted file mode 100644
    index 3cd7357248..0000000000
    --- a/vector/src/main/res/drawable/ic_book.xml
    +++ /dev/null
    @@ -1,21 +0,0 @@
    -
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_call.xml b/vector/src/main/res/drawable/ic_call.xml
    deleted file mode 100644
    index 430c438577..0000000000
    --- a/vector/src/main/res/drawable/ic_call.xml
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_call_conference_small.xml b/vector/src/main/res/drawable/ic_call_conference_small.xml
    deleted file mode 100644
    index 1ba596d4a9..0000000000
    --- a/vector/src/main/res/drawable/ic_call_conference_small.xml
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_call_end.xml b/vector/src/main/res/drawable/ic_call_end.xml
    deleted file mode 100644
    index 0e80a323d8..0000000000
    --- a/vector/src/main/res/drawable/ic_call_end.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_call_flip_camera_active.xml b/vector/src/main/res/drawable/ic_call_flip_camera_active.xml
    deleted file mode 100644
    index 25590cc753..0000000000
    --- a/vector/src/main/res/drawable/ic_call_flip_camera_active.xml
    +++ /dev/null
    @@ -1,11 +0,0 @@
    -
    -    
    -    
    -
    diff --git a/vector/src/main/res/drawable/ic_call_flip_camera_default.xml b/vector/src/main/res/drawable/ic_call_flip_camera_default.xml
    deleted file mode 100644
    index 75ad0133f8..0000000000
    --- a/vector/src/main/res/drawable/ic_call_flip_camera_default.xml
    +++ /dev/null
    @@ -1,7 +0,0 @@
    -
    -    
    -
    diff --git a/vector/src/main/res/drawable/ic_call_mute_active.xml b/vector/src/main/res/drawable/ic_call_mute_active.xml
    deleted file mode 100644
    index 757f9cfa17..0000000000
    --- a/vector/src/main/res/drawable/ic_call_mute_active.xml
    +++ /dev/null
    @@ -1,11 +0,0 @@
    -
    -    
    -    
    -
    diff --git a/vector/src/main/res/drawable/ic_call_mute_default.xml b/vector/src/main/res/drawable/ic_call_mute_default.xml
    deleted file mode 100644
    index 37a0c83fec..0000000000
    --- a/vector/src/main/res/drawable/ic_call_mute_default.xml
    +++ /dev/null
    @@ -1,7 +0,0 @@
    -
    -    
    -
    diff --git a/vector/src/main/res/drawable/ic_call_pip.xml b/vector/src/main/res/drawable/ic_call_pip.xml
    deleted file mode 100644
    index aaad2d09de..0000000000
    --- a/vector/src/main/res/drawable/ic_call_pip.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml b/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml
    deleted file mode 100644
    index 50768871ab..0000000000
    --- a/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -
    -  
    -  
    -    
    -    
    -  
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml b/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml
    deleted file mode 100644
    index 50768871ab..0000000000
    --- a/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -
    -  
    -  
    -    
    -    
    -  
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_flair.xml b/vector/src/main/res/drawable/ic_flair.xml
    deleted file mode 100644
    index 8d30334432..0000000000
    --- a/vector/src/main/res/drawable/ic_flair.xml
    +++ /dev/null
    @@ -1,22 +0,0 @@
    -
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_home_bottom_catchup.xml b/vector/src/main/res/drawable/ic_home_bottom_catchup.xml
    deleted file mode 100644
    index 1091bb04c7..0000000000
    --- a/vector/src/main/res/drawable/ic_home_bottom_catchup.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_lock.xml b/vector/src/main/res/drawable/ic_lock.xml
    deleted file mode 100644
    index 2929f41225..0000000000
    --- a/vector/src/main/res/drawable/ic_lock.xml
    +++ /dev/null
    @@ -1,22 +0,0 @@
    -
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_material_add.xml b/vector/src/main/res/drawable/ic_material_add.xml
    deleted file mode 100644
    index 757f450331..0000000000
    --- a/vector/src/main/res/drawable/ic_material_add.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_more_horizontal_2.xml b/vector/src/main/res/drawable/ic_more_horizontal_2.xml
    deleted file mode 100644
    index 3aab2fada5..0000000000
    --- a/vector/src/main/res/drawable/ic_more_horizontal_2.xml
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -
    -  
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_poll.xml b/vector/src/main/res/drawable/ic_poll.xml
    deleted file mode 100644
    index 581343eb2b..0000000000
    --- a/vector/src/main/res/drawable/ic_poll.xml
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -
    -  
    -  
    -  
    -  
    -
    diff --git a/vector/src/main/res/drawable/ic_radio_button_off.xml b/vector/src/main/res/drawable/ic_radio_button_off.xml
    deleted file mode 100644
    index f4f3f0d268..0000000000
    --- a/vector/src/main/res/drawable/ic_radio_button_off.xml
    +++ /dev/null
    @@ -1,12 +0,0 @@
    -
    -
    -    
    -    
    -
    -
    diff --git a/vector/src/main/res/drawable/red_dot.xml b/vector/src/main/res/drawable/red_dot.xml
    deleted file mode 100644
    index b7fccb7cab..0000000000
    --- a/vector/src/main/res/drawable/red_dot.xml
    +++ /dev/null
    @@ -1,11 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/searches_cursor_background.xml b/vector/src/main/res/drawable/searches_cursor_background.xml
    deleted file mode 100644
    index c9d1d88498..0000000000
    --- a/vector/src/main/res/drawable/searches_cursor_background.xml
    +++ /dev/null
    @@ -1,5 +0,0 @@
    -
    -
    -    
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/vector_medias_picker_button_background.xml b/vector/src/main/res/drawable/vector_medias_picker_button_background.xml
    deleted file mode 100644
    index 8adc855a90..0000000000
    --- a/vector/src/main/res/drawable/vector_medias_picker_button_background.xml
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -    
    -    
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/activity_incoming_share.xml b/vector/src/main/res/layout/activity_incoming_share.xml
    deleted file mode 100644
    index 0de417f57b..0000000000
    --- a/vector/src/main/res/layout/activity_incoming_share.xml
    +++ /dev/null
    @@ -1,46 +0,0 @@
    -
    -
    -
    -    
    -
    -        
    -
    -            
    -
    -                
    -
    -            
    -
    -        
    -
    -        
    -
    -    
    -
    -
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/bottom_sheet_call_dialer_choice.xml b/vector/src/main/res/layout/bottom_sheet_call_dialer_choice.xml
    deleted file mode 100644
    index 0539c648b4..0000000000
    --- a/vector/src/main/res/layout/bottom_sheet_call_dialer_choice.xml
    +++ /dev/null
    @@ -1,27 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -
    diff --git a/vector/src/main/res/layout/bottom_sheet_call_sound_device_chooser.xml b/vector/src/main/res/layout/bottom_sheet_call_sound_device_chooser.xml
    deleted file mode 100644
    index e751ac412a..0000000000
    --- a/vector/src/main/res/layout/bottom_sheet_call_sound_device_chooser.xml
    +++ /dev/null
    @@ -1,60 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -
    diff --git a/vector/src/main/res/layout/fragment_bootstrap_enter_account_password.xml b/vector/src/main/res/layout/fragment_bootstrap_enter_account_password.xml
    deleted file mode 100644
    index b8eefc52ec..0000000000
    --- a/vector/src/main/res/layout/fragment_bootstrap_enter_account_password.xml
    +++ /dev/null
    @@ -1,55 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -        
    -
    -    
    -
    -