From 00bfbe9bc6a3f3c6576cc161d8144dc22d23ba5e Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 7 Oct 2022 11:16:41 -0400 Subject: [PATCH 0001/1068] Adds API parsing of unread threads notifications --- .../sdk/api/session/sync/model/RoomSync.kt | 6 ++++ .../RoomSyncUnreadThreadNotifications.kt | 33 +++++++++++++++++++ .../internal/session/filter/FilterFactory.kt | 12 ++++--- .../session/filter/RoomEventFilter.kt | 10 ++++-- .../room/summary/RoomSummaryUpdater.kt | 4 +++ .../sync/handler/room/RoomSyncHandler.kt | 4 ++- 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt index e5ac0a39b2..472121269f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt @@ -47,6 +47,12 @@ data class RoomSync( */ @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, + + /** + * The count of threads with unread notifications (not the total # of notifications in all threads) + */ + @Json(name = "org.matrix.msc3773.unread_thread_notifications") val unreadThreadNotifications: Map? = null, + /** * The room summary. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt new file mode 100644 index 0000000000..70524d299a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt @@ -0,0 +1,33 @@ +/* + * 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.sync.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RoomSyncUnreadThreadNotifications( + /** + * The number of threads with unread messages that match the push notification rules. + */ + @Json(name = "notification_count") val notificationCount: Int? = null, + + /** + * The number of threads with highlighted unread messages (subset of notifications). + */ + @Json(name = "highlight_count") val highlightCount: Int? = null +) 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 676a4f6a38..141144acd8 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 @@ -28,7 +28,8 @@ internal object FilterFactory { limit = numberOfEvents, // senders = listOf(userId), // relationSenders = userId?.let { listOf(it) }, - relationTypes = listOf(RelationType.THREAD) + relationTypes = listOf(RelationType.THREAD), + enableUnreadThreadNotifications = true, ) } @@ -37,7 +38,8 @@ internal object FilterFactory { limit = numberOfEvents, containsUrl = true, types = listOf(EventType.MESSAGE), - lazyLoadMembers = true + lazyLoadMembers = true, + enableUnreadThreadNotifications = true, ) } @@ -62,7 +64,8 @@ internal object FilterFactory { fun createElementRoomFilter(): RoomEventFilter { return RoomEventFilter( - lazyLoadMembers = true + lazyLoadMembers = true, + enableUnreadThreadNotifications = true, // TODO Enable this for optimization // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() ) @@ -77,7 +80,8 @@ internal object FilterFactory { private fun createElementStateFilter(): RoomEventFilter { return RoomEventFilter( - lazyLoadMembers = true + lazyLoadMembers = true, + enableUnreadThreadNotifications = true, ) } 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 220c401137..81d1c04261 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.filter import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.sync.model.RoomSync import org.matrix.android.sdk.internal.di.MoshiProvider /** @@ -76,7 +77,11 @@ internal data class RoomEventFilter( /** * If true, enables lazy-loading of membership events. See Lazy-loading room members for more information. Defaults to false. */ - @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null + @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null, + /** + * If true, this will opt-in for the server to return unread threads notifications in [RoomSync] + */ + @Json(name = "org.matrix.msc3773.unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null, ) { fun toJSONString(): String { @@ -92,6 +97,7 @@ internal data class RoomEventFilter( rooms != null || notRooms != null || containsUrl != null || - lazyLoadMembers != null) + lazyLoadMembers != null || + enableUnreadThreadNotifications != null) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 6979d42827..537483193a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications +import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadThreadNotifications import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -91,6 +92,7 @@ internal class RoomSummaryUpdater @Inject constructor( membership: Membership? = null, roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, + unreadThreadNotifications: Map? = null, updateMembers: Boolean = false, inviterId: String? = null, aggregator: SyncResponsePostTreatmentAggregator? = null @@ -111,6 +113,8 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0 roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 + // TODO: Handle unreadThreadNotifications + if (membership != null) { roomSummaryEntity.membership = membership } 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 a2f2251b70..2825be8291 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 @@ -287,6 +287,7 @@ internal class RoomSyncHandler @Inject constructor( Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, updateMembers = hasRoomMember, aggregator = aggregator ) @@ -372,7 +373,8 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications, aggregator = aggregator) + roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, + roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) return roomEntity } From ebd8461724430f9a51b1927653ab37ea9d62ad95 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 7 Oct 2022 11:42:18 -0400 Subject: [PATCH 0002/1068] Adds thread notifications and highlights to RoomSummaryEntity --- .../sdk/internal/database/model/RoomSummaryEntity.kt | 10 ++++++++++ .../session/room/summary/RoomSummaryUpdater.kt | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 471bec59af..650dd3c5cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -115,6 +115,16 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var threadNotificationCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var threadHighlightCount: Int = 0 + set(value) { + if (value != field) field = value + } + var readMarkerId: String? = null set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 537483193a..185b7d35d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -113,7 +113,15 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0 roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 - // TODO: Handle unreadThreadNotifications + roomSummaryEntity.threadHighlightCount = unreadThreadNotifications + ?.mapNotNull { it.value.highlightCount } + ?.sum() + ?: 0 + roomSummaryEntity.threadNotificationCount = unreadThreadNotifications + ?.mapNotNull { it.value.notificationCount } + ?.sum() + ?: 0 + if (membership != null) { roomSummaryEntity.membership = membership From c2ae75d9bdbdbdf3ba9d180ea24f0c382a16c404 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 7 Oct 2022 11:45:58 -0400 Subject: [PATCH 0003/1068] Changes thread notifications saved to entity from sum to size --- .../internal/session/room/summary/RoomSummaryUpdater.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 185b7d35d6..c740e07257 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -114,12 +114,12 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 roomSummaryEntity.threadHighlightCount = unreadThreadNotifications - ?.mapNotNull { it.value.highlightCount } - ?.sum() + ?.map { it.value.highlightCount.takeIf { count -> (count ?: 0) > 0 } } + ?.size ?: 0 roomSummaryEntity.threadNotificationCount = unreadThreadNotifications - ?.mapNotNull { it.value.notificationCount } - ?.sum() + ?.map { it.value.notificationCount.takeIf { count -> (count ?: 0) > 0 } } + ?.size ?: 0 From a2382c6a0157ada1b8129f906b340bdc7c8893bb Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Fri, 7 Oct 2022 11:47:18 -0400 Subject: [PATCH 0004/1068] Adds thread notification fields to RoomSummary --- .../android/sdk/api/session/room/model/RoomSummary.kt | 8 ++++++++ .../sdk/internal/database/mapper/RoomSummaryMapper.kt | 2 ++ 2 files changed, 10 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index ff4977491f..6058564b78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -97,6 +97,14 @@ data class RoomSummary( * Number of unread and highlighted message in this room. */ val highlightCount: Int = 0, + /** + * Number of threads with unread messages in this room + */ + val threadNotificationCount: Int = 0, + /** + * Number of threads with highlighted messages in this room + */ + val threadHighlightCount: Int = 0, /** * True if this room has unread messages. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 72b0f7a043..6e9fff78e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -61,6 +61,8 @@ internal class RoomSummaryMapper @Inject constructor( otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, + threadHighlightCount = roomSummaryEntity.threadHighlightCount, + threadNotificationCount = roomSummaryEntity.threadNotificationCount, hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, tags = tags, typingUsers = typingUsers, From 1db6b7be1ffab1f1fd8491de102dfd8ba533dc46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 23:05:08 +0000 Subject: [PATCH 0005/1068] Bump danger/danger-js from 11.1.3 to 11.1.4 Bumps [danger/danger-js](https://github.com/danger/danger-js) from 11.1.3 to 11.1.4. - [Release notes](https://github.com/danger/danger-js/releases) - [Changelog](https://github.com/danger/danger-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/danger/danger-js/compare/11.1.3...11.1.4) --- updated-dependencies: - dependency-name: danger/danger-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/danger.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 5698a696b6..30b6600c94 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.1.3 + uses: danger/danger-js@11.1.4 with: args: "--dangerfile tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1692e2e281..9d9e8e76e8 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,7 +66,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.1.3 + uses: danger/danger-js@11.1.4 with: args: "--dangerfile tools/danger/dangerfile-lint.js" env: From 4feb60145b8d6a56f7c8d7d106f568f762008fa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 23:06:56 +0000 Subject: [PATCH 0006/1068] Bump appDistribution from 16.0.0-beta04 to 16.0.0-beta05 Bumps `appDistribution` from 16.0.0-beta04 to 16.0.0-beta05. Updates `firebase-appdistribution-api-ktx` from 16.0.0-beta04 to 16.0.0-beta05 Updates `firebase-appdistribution` from 16.0.0-beta04 to 16.0.0-beta05 --- updated-dependencies: - dependency-name: com.google.firebase:firebase-appdistribution-api-ktx dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.google.firebase:firebase-appdistribution dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index edff769070..30bd576c21 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -12,7 +12,7 @@ def gradle = "7.2.2" def kotlin = "1.7.20" def kotlinCoroutines = "1.6.4" def dagger = "2.44" -def appDistribution = "16.0.0-beta04" +def appDistribution = "16.0.0-beta05" def retrofit = "2.9.0" def markwon = "4.6.2" def moshi = "1.14.0" From 9198cc7ac072c11fb5d038b0d232ade2102ca2ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 23:07:54 +0000 Subject: [PATCH 0007/1068] Bump android-connector from 2.1.0 to 2.1.1 Bumps [android-connector](https://github.com/UnifiedPush/android-connector) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/UnifiedPush/android-connector/releases) - [Commits](https://github.com/UnifiedPush/android-connector/compare/2.1.0...2.1.1) --- updated-dependencies: - dependency-name: com.github.UnifiedPush:android-connector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index c59e1a3028..32579d0ccc 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -236,7 +236,7 @@ dependencies { implementation libs.sentry.sentryAndroid // UnifiedPush - implementation 'com.github.UnifiedPush:android-connector:2.1.0' + implementation 'com.github.UnifiedPush:android-connector:2.1.1' implementation "androidx.emoji2:emoji2:1.2.0" From b274bf0760824c2b7849c2c11706c1857952e3ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 23:07:58 +0000 Subject: [PATCH 0008/1068] Bump firebase-messaging from 23.0.8 to 23.1.0 Bumps firebase-messaging from 23.0.8 to 23.1.0. --- updated-dependencies: - dependency-name: com.google.firebase:firebase-messaging dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- vector-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index eb19027880..47bd928f17 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -373,7 +373,7 @@ dependencies { gplayImplementation "com.google.android.gms:play-services-location:20.0.0" // UnifiedPush gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') { + gplayImplementation('com.google.firebase:firebase-messaging:23.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' From e875d9d329e5131881166465fc31a51d1051541a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 13 Oct 2022 11:52:35 +0200 Subject: [PATCH 0009/1068] Ensure the latest paparazzi version is used, when updated by Dependabot. Dependabot can update the plugin version, but not the library we add manually. --- build.gradle | 4 ++-- dependencies.gradle | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index e474b68531..486da53e98 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' - classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.1.0' + classpath libs.squareup.paparazziPlugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -322,7 +322,7 @@ ext.initScreenshotTests = { project -> if (hasScreenshots) { project.apply plugin: 'app.cash.paparazzi' } - project.dependencies { testCompileOnly "app.cash.paparazzi:paparazzi:1.0.0" } + project.dependencies { testCompileOnly libs.squareup.paparazzi } project.android.testOptions.unitTests.all { def screenshotTestCapture = "**/*ScreenshotTest*" if (hasScreenshots) { diff --git a/dependencies.gradle b/dependencies.gradle index edff769070..3b39f72965 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,5 +1,4 @@ ext.versions = [ - 'minSdk' : 21, 'compileSdk' : 32, 'targetSdk' : 32, @@ -27,22 +26,20 @@ def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" - def sentry = "6.4.3" - def fragment = "1.5.3" - // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.4.0" def androidxTest = "1.4.0" def androidxOrchestrator = "1.4.1" +def paparazzi = "1.1.0" + ext.libs = [ gradle : [ 'gradlePlugin' : "com.android.tools.build:gradle:$gradle", 'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin", 'hiltPlugin' : "com.google.dagger:hilt-android-gradle-plugin:$dagger" - ], jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", @@ -108,6 +105,8 @@ ext.libs = [ 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi", 'moshiAdapters' : "com.squareup.moshi:moshi-adapters:$moshi", + 'paparazzi' : "app.cash.paparazzi:paparazzi:$paparazzi", + 'paparazziPlugin' : "app.cash.paparazzi:paparazzi-gradle-plugin:$paparazzi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" ], From 21ae4c6ddbf29bd33f3888788717f0cbd06d74b5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:11:15 +0100 Subject: [PATCH 0010/1068] Support for login by m.login.token during QR code sign in --- .../sdk/api/auth/AuthenticationService.kt | 14 +++ .../matrix/android/sdk/api/auth/LoginType.kt | 4 +- .../auth/DefaultAuthenticationService.kt | 15 +++- .../sdk/internal/auth/data/LoginParams.kt | 2 + .../internal/auth/data/PasswordLoginParams.kt | 4 +- .../internal/auth/data/TokenLoginParams.kt | 4 +- .../internal/auth/login/QrLoginTokenTask.kt | 88 +++++++++++++++++++ 7 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 5ae70e1978..8f2a784d49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -124,4 +124,18 @@ interface AuthenticationService { initialDeviceName: String, deviceId: String? = null ): Session + + /** + * Authenticate using m.login.token method during sign in with QR code. + * @param homeServerConnectionConfig the information about the homeserver and other configuration + * @param loginToken the m.login.token + * @param initialDeviceName the initial device name + * @param deviceId the device id, optional. If not provided or null, the server will generate one. + */ + suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String, + deviceId: String? = null + ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt index 627a825679..991b7b654d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt @@ -22,7 +22,8 @@ enum class LoginType { UNSUPPORTED, CUSTOM, DIRECT, - UNKNOWN; + UNKNOWN, + QR; companion object { @@ -32,6 +33,7 @@ enum class LoginType { UNSUPPORTED.name -> UNSUPPORTED CUSTOM.name -> CUSTOM DIRECT.name -> DIRECT + QR.name -> QR else -> UNKNOWN } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 446f931847..90dc57b4f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices @@ -62,7 +63,8 @@ internal class DefaultAuthenticationService @Inject constructor( private val sessionCreator: SessionCreator, private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, - private val directLoginTask: DirectLoginTask + private val directLoginTask: DirectLoginTask, + private val loginTokenAuthTask: QrLoginTokenTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -404,6 +406,17 @@ internal class DefaultAuthenticationService @Inject constructor( ) } + override suspend fun loginUsingQrLoginToken(homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, initialDeviceName: String, deviceId: String?): Session { + return loginTokenAuthTask.execute( + QrLoginTokenTask.Params( + homeServerConnectionConfig = homeServerConnectionConfig, + loginToken = loginToken, + deviceName = initialDeviceName, + deviceId = deviceId + ) + ) + } + private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString()) return retrofit.create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt index ea8578ed8c..8646752083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt @@ -18,4 +18,6 @@ package org.matrix.android.sdk.internal.auth.data internal interface LoginParams { val type: String + val deviceDisplayName: String? + val deviceId: String? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt index 5f0a2298cb..062b2466e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt @@ -30,8 +30,8 @@ internal data class PasswordLoginParams( @Json(name = "identifier") val identifier: Map, @Json(name = "password") val password: String, @Json(name = "type") override val type: String, - @Json(name = "initial_device_display_name") val deviceDisplayName: String?, - @Json(name = "device_id") val deviceId: String? + @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, + @Json(name = "device_id") override val deviceId: String? ) : LoginParams { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt index 0c6fb45e78..22cc185fa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt @@ -23,5 +23,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes @JsonClass(generateAdapter = true) internal data class TokenLoginParams( @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN, - @Json(name = "token") val token: String + @Json(name = "token") val token: String, + @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, + @Json(name = "device_id") override val deviceId: String? ) : LoginParams diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt new file mode 100644 index 0000000000..477719f607 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt @@ -0,0 +1,88 @@ +/* + * 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.auth.login + +import dagger.Lazy +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.auth.SessionCreator +import org.matrix.android.sdk.internal.auth.data.TokenLoginParams +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface QrLoginTokenTask : Task { + data class Params( + val homeServerConnectionConfig: HomeServerConnectionConfig, + val loginToken: String, + val deviceName: String?, + val deviceId: String? + ) +} + +internal class DefaultQrLoginTokenTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val sessionCreator: SessionCreator, +) : QrLoginTokenTask { + + override suspend fun execute(params: QrLoginTokenTask.Params): Session { + val client = buildClient(params.homeServerConnectionConfig) + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() + + val authAPI = retrofitFactory.create(client, homeServerUrl) + .create(AuthAPI::class.java) + + val loginParams = TokenLoginParams( + token = params.loginToken, + deviceDisplayName = params.deviceName, + deviceId = params.deviceId + ) + + val credentials = try { + executeRequest(null) { + authAPI.login(loginParams) + } + } catch (throwable: Throwable) { + throw when (throwable) { + is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + else -> throwable + } + } + + return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.QR) + } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } +} From 098c268af3c348ce8ce7ef088047e907cd1efe83 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:18:31 +0100 Subject: [PATCH 0011/1068] Changelog --- changelog.d/7358.sdk | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7358.sdk diff --git a/changelog.d/7358.sdk b/changelog.d/7358.sdk new file mode 100644 index 0000000000..3d17076a44 --- /dev/null +++ b/changelog.d/7358.sdk @@ -0,0 +1 @@ +Add support for `m.login.token` auth during QR code based sign in From a71ecee44a5b39d1ac13faa0078e0349d0bbf901 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:19:39 +0100 Subject: [PATCH 0012/1068] Linting --- .../sdk/internal/auth/DefaultAuthenticationService.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 90dc57b4f0..7fd730bece 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -406,7 +406,12 @@ internal class DefaultAuthenticationService @Inject constructor( ) } - override suspend fun loginUsingQrLoginToken(homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, initialDeviceName: String, deviceId: String?): Session { + override suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String, + deviceId: String?, + ): Session { return loginTokenAuthTask.execute( QrLoginTokenTask.Params( homeServerConnectionConfig = homeServerConnectionConfig, From 0d245657e1fd50596397e4dd48d70c24c8661fba Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:32:49 +0100 Subject: [PATCH 0013/1068] Retry scanning if not a QR code From 1c70d455fbfda7a301ed01f1f18eef1ddf2aa902 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:33:30 +0100 Subject: [PATCH 0014/1068] Implementations of MSC3886 and MSC3903 From b192fdb0a89b1aa922440ae3dda3bf288a8b7611 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:34:05 +0100 Subject: [PATCH 0015/1068] Partial implementation of QR login logic --- .../features/login/qr/QrCodeLoginViewModel.kt | 2 +- .../voicebroadcast/VoiceBroadcastConstants.kt | 20 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index f97a59b432..d0c34b83af 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -85,8 +85,8 @@ class QrCodeLoginViewModel @AssistedInject constructor( Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") confirmationCode ?.let { onConnectionEstablished(it) - rendezvous.completeOnNewDevice() } + rendezvous.completeOnNewDevice() } // if (isValidQrCode(action.qrCode)) { // setState { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt deleted file mode 100644 index d7d74b08e9..0000000000 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.voicebroadcast - -/** Voice Broadcast State Event. */ -const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" From 6e09d900070319b356a2b10a1769af8da97a2aef Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 12 Oct 2022 14:32:09 +0300 Subject: [PATCH 0016/1068] Merge branch 'develop' into feature/ons/qr_code_login_ui # Conflicts: # library/ui-strings/src/main/res/values/strings.xml # library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml # vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt # vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt # vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt # vector/src/main/java/im/vector/app/features/VectorFeatures.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt # vector/src/main/res/layout/fragment_other_sessions.xml # vector/src/main/res/layout/fragment_settings_devices.xml --- .../voicebroadcast/VoiceBroadcastConstants.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt new file mode 100644 index 0000000000..d7d74b08e9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.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 im.vector.app.features.voicebroadcast + +/** Voice Broadcast State Event. */ +const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" From 86090086b1344a8e80f8bd3accb1cbbf26a2eab2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 12 Oct 2022 13:08:01 +0100 Subject: [PATCH 0017/1068] Only do completeOnNewDevice if we received a confirmation code --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index d0c34b83af..f97a59b432 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -85,8 +85,8 @@ class QrCodeLoginViewModel @AssistedInject constructor( Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") confirmationCode ?.let { onConnectionEstablished(it) + rendezvous.completeOnNewDevice() } - rendezvous.completeOnNewDevice() } // if (isValidQrCode(action.qrCode)) { // setState { From ebb3d201c18efba47ec546d778c2f311f266ff9e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:35:45 +0100 Subject: [PATCH 0018/1068] Make initialDeviceName optional --- .../org/matrix/android/sdk/api/auth/AuthenticationService.kt | 2 +- .../android/sdk/internal/auth/DefaultAuthenticationService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 8f2a784d49..c8065e4524 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -135,7 +135,7 @@ interface AuthenticationService { suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String, + initialDeviceName: String?, deviceId: String? = null ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 7fd730bece..6c3622ed5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -409,7 +409,7 @@ internal class DefaultAuthenticationService @Inject constructor( override suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String, + initialDeviceName: String?, deviceId: String?, ): Session { return loginTokenAuthTask.execute( From 5843c3832b6e654a813dde273c75cf9e68497e63 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:58:19 +0100 Subject: [PATCH 0019/1068] Use correct var name --- .../android/sdk/internal/auth/DefaultAuthenticationService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 6c3622ed5d..5b12e3bdc3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -64,7 +64,7 @@ internal class DefaultAuthenticationService @Inject constructor( private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, private val directLoginTask: DirectLoginTask, - private val loginTokenAuthTask: QrLoginTokenTask + private val qrLoginTokenTask: QrLoginTokenTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -412,7 +412,7 @@ internal class DefaultAuthenticationService @Inject constructor( initialDeviceName: String?, deviceId: String?, ): Session { - return loginTokenAuthTask.execute( + return qrLoginTokenTask.execute( QrLoginTokenTask.Params( homeServerConnectionConfig = homeServerConnectionConfig, loginToken = loginToken, From 579df742579f19fda49913d031759f26ea61c841 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:02:57 +0100 Subject: [PATCH 0020/1068] Add missing binding --- .../java/org/matrix/android/sdk/internal/auth/AuthModule.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index 463692e574..b1f65194f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.auth.db.AuthRealmModule import org.matrix.android.sdk.internal.auth.db.RealmPendingSessionStore import org.matrix.android.sdk.internal.auth.db.RealmSessionParamsStore import org.matrix.android.sdk.internal.auth.login.DefaultDirectLoginTask +import org.matrix.android.sdk.internal.auth.login.DefaultQrLoginTokenTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthDatabase import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter @@ -94,4 +96,7 @@ internal abstract class AuthModule { @Binds abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService + + @Binds + abstract fun bindQrLoginTokenTask(task: DefaultQrLoginTokenTask): QrLoginTokenTask } From b5e81d27d65bb8dc5819250c29035e004c24121f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:08:50 +0100 Subject: [PATCH 0021/1068] Set default value for optional params --- .../matrix/android/sdk/internal/auth/data/TokenLoginParams.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt index 22cc185fa7..52045a1d7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt @@ -24,6 +24,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes internal data class TokenLoginParams( @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN, @Json(name = "token") val token: String, - @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, - @Json(name = "device_id") override val deviceId: String? + @Json(name = "initial_device_display_name") override val deviceDisplayName: String? = null, + @Json(name = "device_id") override val deviceId: String? = null ) : LoginParams From 22b344c43a224a52d45be8087a7ead5678d6b38f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:11:41 +0100 Subject: [PATCH 0022/1068] Another default value fix --- .../org/matrix/android/sdk/api/auth/AuthenticationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index c8065e4524..e490311b91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -135,7 +135,7 @@ interface AuthenticationService { suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String?, + initialDeviceName: String? = null, deviceId: String? = null ): Session } From 8dbb1b830ef8c508190b0367501a1ef441c717c6 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:28:47 +0100 Subject: [PATCH 0023/1068] Map for soft logout --- .../im/vector/app/features/signout/soft/SoftLogoutController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt index b1a240e942..a1ed27df1d 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt @@ -152,6 +152,7 @@ class SoftLogoutController @Inject constructor( LoginType.SSO -> buildLoginSSOForm() LoginType.DIRECT, LoginType.CUSTOM, + LoginType.QR, LoginType.UNSUPPORTED -> buildLoginUnsupportedForm() LoginType.UNKNOWN -> Unit } From 6a653c33ae053d73e2d55e640a4b4bfccc74e648 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Thu, 13 Oct 2022 17:09:11 +0100 Subject: [PATCH 0024/1068] Add issue automation for PS feature teams --- .github/workflows/triage-labelled.yml | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 174e3c54c0..4031e9d571 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -246,3 +246,81 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc4ABTXY" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features1: + name: Add labelled issues to PS features team 1 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Polls') || + contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || + (contains(github.event.issue.labels.*.name, 'A-Voice-Messages') && + !contains(github.event.issue.labels.*.name, 'A-Broadcast')) || + (contains(github.event.issue.labels.*.name, 'A-Session-Mgmt') && + contains(github.event.issue.labels.*.name, 'A-User-Settings')) + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKF" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features2: + name: Add labelled issues to PS features team 2 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-DM-Start') || + contains(github.event.issue.labels.*.name, 'A-Broadcast') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKd" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features3: + name: Add labelled issues to PS features team 3 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Composer-WYSIWYG') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKW" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} From 0111b932dea08e98362182165a23d717bea68592 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:08:45 +0100 Subject: [PATCH 0025/1068] Support for navigation to home screen --- .../im/vector/app/features/login/qr/QrCodeLoginActivity.kt | 7 +++++++ .../vector/app/features/login/qr/QrCodeLoginViewEvents.kt | 1 + 2 files changed, 8 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt index e0323fdc2d..3c7b0a4729 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt @@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.features.home.HomeActivity import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber @@ -75,6 +76,7 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { when (it) { QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen() QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen() + QrCodeLoginViewEvents.NavigateToHomeScreen -> handleNavigateToHomeScreen() } } } @@ -95,6 +97,11 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { ) } + private fun handleNavigateToHomeScreen() { + val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true) + startActivity(intent) + } + companion object { private const val FRAGMENT_QR_CODE_INSTRUCTIONS_TAG = "FRAGMENT_QR_CODE_INSTRUCTIONS_TAG" diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt index dc258408e7..0f282fee38 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class QrCodeLoginViewEvents : VectorViewEvents { object NavigateToStatusScreen : QrCodeLoginViewEvents() object NavigateToShowQrCodeScreen : QrCodeLoginViewEvents() + object NavigateToHomeScreen : QrCodeLoginViewEvents() } From 1ed082d3cb16943a15664dbb862126942f5539d3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:15:52 +0100 Subject: [PATCH 0026/1068] QR login + E2EE set up --- .../sdk/internal/rendezvous/Rendezvous.kt | 95 +++++++++---------- .../features/login/qr/QrCodeLoginViewModel.kt | 73 ++++++-------- 2 files changed, 74 insertions(+), 94 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt index dd7b91582b..2f85a97c55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt @@ -16,8 +16,11 @@ package org.matrix.android.sdk.internal.rendezvous +import android.net.Uri import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel @@ -56,9 +59,12 @@ internal data class Payload( private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value -data class Rendezvous( +/** + * Implementation of MSC3906 to sign in + E2EE set up using a QR code. + */ +class Rendezvous( val channel: RendezvousChannel, - val theirIntent: RendezvousIntent + val theirIntent: RendezvousIntent, ) { companion object { fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { @@ -116,7 +122,7 @@ data class Rendezvous( return checksum } - suspend fun completeOnNewDevice(): Session? { + suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { Timber.tag(TAG).i("Waiting for login_token"); val loginToken = receive() @@ -143,59 +149,46 @@ data class Rendezvous( Timber.tag(TAG).i("Got login_token: $login_token for $homeserver"); - // TODO: set view to be state logging in? + val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver)) + return authenticationService.loginUsingQrLoginToken(hsConfig, login_token) + } - // use token to login -// const login = await sendLoginRequest(homeserver, undefined, "m.login.token", { token: login_token }); -// -// await setLoggedIn(login); -// -// const { deviceId, userId } = login; -// -// const client = MatrixClientPeg.get(); -// + suspend fun completeVerificationOnNewDevice(session: Session) { + val userId = session.myUserId + val crypto = session.cryptoService() + val deviceId = crypto.getMyDevice().deviceId + val deviceKey = crypto.getMyDevice().fingerprint() + send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) - val newSession: Session? = null + // await confirmation of verification - newSession ?.let { - session -> - val userId = session.myUserId - val crypto = session.cryptoService() - val deviceId = crypto.getMyDevice().deviceId - val deviceKey = crypto.getMyDevice().fingerprint() - send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) - - // await confirmation of verification - - val verificationResponse = receive() - val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") - val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) - if (verifyingDeviceFromServer?.fingerprint() == verificationResponse.verifying_device_key) { - // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - - verificationResponse.master_key ?.let { - // set master key as trusted - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) - - } - - // request secrets from the verifying device - Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") - - session.sharedSecretStorageService() .let { - it.requestSecret(verifyingDeviceId, MASTER_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, SELF_SIGNING_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, USER_SIGNING_KEY_SSSS_NAME) - it.requestSecret(verifyingDeviceId, KEYBACKUP_SECRET_SSSS_NAME) - } - } else { - Timber.tag(TAG).i("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") - } + val verificationResponse = receive() + val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) { + Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") + return; } - return newSession + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + // TODO: what do we do with the master key? +// verificationResponse.master_key ?.let { +// // set master key as trusted +// crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) +// } + + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + + session.sharedSecretStorageService() .let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + } } private suspend fun receive(): Payload? { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index f97a59b432..c729152e44 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -16,24 +16,31 @@ package im.vector.app.features.login.qr +import android.content.Context import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.home.HomeActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.rendezvous.Rendezvous import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( - @Assisted private val initialState: QrCodeLoginViewState + @Assisted private val initialState: QrCodeLoginViewState, + private val applicationContext: Context, + private val authenticationService: AuthenticationService, + private val activeSessionHolder: ActiveSessionHolder, ) : VectorViewModel(initialState) { - val TAG: String = QrCodeLoginViewModel::class.java.simpleName @AssistedFactory @@ -81,44 +88,31 @@ class QrCodeLoginViewModel @AssistedInject constructor( _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) viewModelScope.launch(Dispatchers.IO) { - val confirmationCode = rendezvous.startAfterScanningCode() - Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") - confirmationCode ?.let { - onConnectionEstablished(it) - rendezvous.completeOnNewDevice() + try { + val confirmationCode = rendezvous.startAfterScanningCode() + Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") + confirmationCode?.let { + onConnectionEstablished(it) + val session = rendezvous.waitForLoginOnNewDevice(authenticationService) + onSigningIn() + session?.let { + activeSessionHolder.setActiveSession(session) + authenticationService.reset() + + session.configureAndStart(applicationContext) + + rendezvous.completeVerificationOnNewDevice(session) + + _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) + } + } + } catch (failure: Throwable) { + Timber.tag(TAG).e(failure, "Error occurred during sign in") + onFailed(RendezvousFailureReason.Unknown) } } - // if (isValidQrCode(action.qrCode)) { -// setState { -// copy( -// connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice -// ) -// } -// _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) -// } -// - -// // TODO. UI test purpose. Fixme remove! -// viewModelScope.launch { -// delay(3000) -// onFailed(QrCodeLoginErrorType.TIMEOUT, true) -// delay(3000) -// onConnectionEstablished("1234-ABCD-5678-EFGH") -// delay(3000) -// onSigningIn() -// delay(3000) -// onFailed(QrCodeLoginErrorType.DEVICE_IS_NOT_SUPPORTED, false) -// } -// // TODO. UI test purpose. Fixme remove! -// viewModelScope.launch { -// delay(3000) -// onConnectionEstablished("1234-ABCD-5678-EFGH") -// delay(3000) -// onSigningIn() -// } } - private fun onFailed(reason: RendezvousFailureReason) { setState { copy( @@ -144,13 +138,6 @@ class QrCodeLoginViewModel @AssistedInject constructor( } } - /** - * TODO. UI test purpose. Fixme accordingly. - */ - private fun isValidQrCode(qrCode: String): Boolean { - return qrCode.startsWith("http") - } - /** * TODO. UI test purpose. Fixme accordingly. */ From 560fda51d163695d1c4878751d9f2f0ab92014b7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:26:22 +0100 Subject: [PATCH 0027/1068] Reduce logging --- .../rendezvous/channels/ECDHRendezvousChannel.kt | 16 ++++++++-------- .../transports/SimpleHttpRendezvousTransport.kt | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt index 33837bc425..cced29aab4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt @@ -94,7 +94,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu val isInitiator = theirPublicKey == null if (isInitiator) { - Timber.tag(TAG).i("Waiting for other device to send their public key") +// Timber.tag(TAG).i("Waiting for other device to send their public key") val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") if (res.key == null) { @@ -106,7 +106,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) } else { // send our public key unencrypted - Timber.tag(TAG).i("Sending public key") +// Timber.tag(TAG).i("Sending public key") send(ECDHPayload( algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) @@ -121,10 +121,10 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu aesKey = olmSAS!!.generateShortCode(aesInfo, 32) - Timber.tag(TAG).i("Our public key: ${Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)}") - Timber.tag(TAG).i("Their public key: ${Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)}") - Timber.tag(TAG).i("AES info: $aesInfo") - Timber.tag(TAG).i("AES key: ${Base64.encodeToString(aesKey, Base64.NO_WRAP)}") +// Timber.tag(TAG).i("Our public key: ${Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)}") +// Timber.tag(TAG).i("Their public key: ${Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)}") +// Timber.tag(TAG).i("AES info: $aesInfo") +// Timber.tag(TAG).i("AES key: ${Base64.encodeToString(aesKey, Base64.NO_WRAP)}") val rawChecksum = olmSAS!!.generateShortCode(aesInfo, 5) return getDecimalCodeRepresentation(rawChecksum) @@ -180,7 +180,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } private fun encrypt(plainText: ByteArray): ECDHPayload { - Timber.tag(TAG).i("Encrypting: ${plainText.toString(Charsets.UTF_8)}") +// Timber.tag(TAG).d("Encrypting: ${plainText.toString(Charsets.UTF_8)}") val iv = ByteArray(16) SecureRandom().nextBytes(iv) @@ -212,7 +212,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu val plainTextBytes = plainText.toByteArray() - Timber.tag(TAG).i("Decrypted: ${plainTextBytes.toString(Charsets.UTF_8)}") +// Timber.tag(TAG).d("Decrypted: ${plainTextBytes.toString(Charsets.UTF_8)}") return plainTextBytes } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 3e5e1121ea..cc4346d55e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -67,7 +67,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo // TODO: properly determine endpoint val uri = if (uri != null) uri!! else "https://rendezvous.lab.element.dev" - Timber.tag(TAG).i("Sending data: ${data.toString(Charsets.UTF_8)} to $uri") +// Timber.tag(TAG).i("Sending data: ${data.toString(Charsets.UTF_8)} to $uri") val httpClient = okhttp3.OkHttpClient.Builder().build() @@ -123,8 +123,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo val response = httpClient.newCall(request.build()).execute() try { - - Timber.tag(TAG).i("Received polling response: ${response.code} from $uri") +// Timber.tag(TAG).d("Received polling response: ${response.code} from $uri") if (response.code == 404) { cancel(RendezvousFailureReason.Unknown) @@ -142,7 +141,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo etag = it } val data = response.body?.bytes() - Timber.tag(TAG).i("Received data: ${data?.toString(Charsets.UTF_8)} from $uri with etag $etag") +// Timber.tag(TAG).d("Received data: ${data?.toString(Charsets.UTF_8)} from $uri with etag $etag") return data } @@ -158,6 +157,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo override suspend fun cancel(reason: RendezvousFailureReason) { var mappedReason = reason + Timber.tag(TAG).i("$expiresAt") if (mappedReason == RendezvousFailureReason.Unknown && expiresAt != null && Date() > expiresAt) { mappedReason = RendezvousFailureReason.Expired From 88238c0f04648a7468c2ee33d86068fa79e4fe5f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:11:15 +0100 Subject: [PATCH 0028/1068] Support for login by m.login.token during QR code sign in --- .../sdk/api/auth/AuthenticationService.kt | 13 +++ .../matrix/android/sdk/api/auth/LoginType.kt | 4 +- .../auth/DefaultAuthenticationService.kt | 15 +++- .../sdk/internal/auth/data/LoginParams.kt | 2 + .../internal/auth/data/PasswordLoginParams.kt | 4 +- .../internal/auth/data/TokenLoginParams.kt | 4 +- .../internal/auth/login/QrLoginTokenTask.kt | 88 +++++++++++++++++++ 7 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index d040f9c67b..6bb47dda5b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -130,4 +130,17 @@ interface AuthenticationService { * Return true if qr code login is supported by the server, false otherwise. */ suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean + * Authenticate using m.login.token method during sign in with QR code. + * @param homeServerConnectionConfig the information about the homeserver and other configuration + * @param loginToken the m.login.token + * @param initialDeviceName the initial device name + * @param deviceId the device id, optional. If not provided or null, the server will generate one. + */ + + suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String, + deviceId: String? = null + ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt index 627a825679..991b7b654d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt @@ -22,7 +22,8 @@ enum class LoginType { UNSUPPORTED, CUSTOM, DIRECT, - UNKNOWN; + UNKNOWN, + QR; companion object { @@ -32,6 +33,7 @@ enum class LoginType { UNSUPPORTED.name -> UNSUPPORTED CUSTOM.name -> CUSTOM DIRECT.name -> DIRECT + QR.name -> QR else -> UNKNOWN } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 4f45f807df..5b6f0686c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices @@ -64,7 +65,8 @@ internal class DefaultAuthenticationService @Inject constructor( private val sessionCreator: SessionCreator, private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, - private val directLoginTask: DirectLoginTask + private val directLoginTask: DirectLoginTask, + private val loginTokenAuthTask: QrLoginTokenTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -420,6 +422,17 @@ internal class DefaultAuthenticationService @Inject constructor( } } + override suspend fun loginUsingQrLoginToken(homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, initialDeviceName: String, deviceId: String?): Session { + return loginTokenAuthTask.execute( + QrLoginTokenTask.Params( + homeServerConnectionConfig = homeServerConnectionConfig, + loginToken = loginToken, + deviceName = initialDeviceName, + deviceId = deviceId + ) + ) + } + private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString()) return retrofit.create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt index ea8578ed8c..8646752083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt @@ -18,4 +18,6 @@ package org.matrix.android.sdk.internal.auth.data internal interface LoginParams { val type: String + val deviceDisplayName: String? + val deviceId: String? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt index 5f0a2298cb..062b2466e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt @@ -30,8 +30,8 @@ internal data class PasswordLoginParams( @Json(name = "identifier") val identifier: Map, @Json(name = "password") val password: String, @Json(name = "type") override val type: String, - @Json(name = "initial_device_display_name") val deviceDisplayName: String?, - @Json(name = "device_id") val deviceId: String? + @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, + @Json(name = "device_id") override val deviceId: String? ) : LoginParams { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt index 0c6fb45e78..22cc185fa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt @@ -23,5 +23,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes @JsonClass(generateAdapter = true) internal data class TokenLoginParams( @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN, - @Json(name = "token") val token: String + @Json(name = "token") val token: String, + @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, + @Json(name = "device_id") override val deviceId: String? ) : LoginParams diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt new file mode 100644 index 0000000000..477719f607 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt @@ -0,0 +1,88 @@ +/* + * 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.auth.login + +import dagger.Lazy +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.auth.SessionCreator +import org.matrix.android.sdk.internal.auth.data.TokenLoginParams +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface QrLoginTokenTask : Task { + data class Params( + val homeServerConnectionConfig: HomeServerConnectionConfig, + val loginToken: String, + val deviceName: String?, + val deviceId: String? + ) +} + +internal class DefaultQrLoginTokenTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + private val retrofitFactory: RetrofitFactory, + private val sessionCreator: SessionCreator, +) : QrLoginTokenTask { + + override suspend fun execute(params: QrLoginTokenTask.Params): Session { + val client = buildClient(params.homeServerConnectionConfig) + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() + + val authAPI = retrofitFactory.create(client, homeServerUrl) + .create(AuthAPI::class.java) + + val loginParams = TokenLoginParams( + token = params.loginToken, + deviceDisplayName = params.deviceName, + deviceId = params.deviceId + ) + + val credentials = try { + executeRequest(null) { + authAPI.login(loginParams) + } + } catch (throwable: Throwable) { + throw when (throwable) { + is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + else -> throwable + } + } + + return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.QR) + } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } +} From 282825db7952fd6516a3b1531fb709ca6831cd78 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:18:31 +0100 Subject: [PATCH 0029/1068] Changelog --- changelog.d/7358.sdk | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7358.sdk diff --git a/changelog.d/7358.sdk b/changelog.d/7358.sdk new file mode 100644 index 0000000000..3d17076a44 --- /dev/null +++ b/changelog.d/7358.sdk @@ -0,0 +1 @@ +Add support for `m.login.token` auth during QR code based sign in From d0898a2b89e7ff0c2858fece93656a2b0427a19e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:19:39 +0100 Subject: [PATCH 0030/1068] Linting --- .../sdk/internal/auth/DefaultAuthenticationService.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 5b6f0686c4..773f2118b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -422,7 +422,12 @@ internal class DefaultAuthenticationService @Inject constructor( } } - override suspend fun loginUsingQrLoginToken(homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, initialDeviceName: String, deviceId: String?): Session { + override suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String, + deviceId: String?, + ): Session { return loginTokenAuthTask.execute( QrLoginTokenTask.Params( homeServerConnectionConfig = homeServerConnectionConfig, From 3b3e11e5681874a7540e0885ee44da1885d29e30 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:32:49 +0100 Subject: [PATCH 0031/1068] Retry scanning if not a QR code From de611ca81a1c9f16ded4b6b8d3d48b12b7edd68e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:33:30 +0100 Subject: [PATCH 0032/1068] Implementations of MSC3886 and MSC3903 From bfab07d716bddf91b2e7cab707dd2fe8e5ffb982 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 11 Oct 2022 23:34:05 +0100 Subject: [PATCH 0033/1068] Partial implementation of QR login logic --- .../features/login/qr/QrCodeLoginViewModel.kt | 27 ++++++++++++++----- .../voicebroadcast/VoiceBroadcastConstants.kt | 20 -------------- 2 files changed, 21 insertions(+), 26 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index da3348653c..05344f0a67 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -59,13 +59,28 @@ class QrCodeLoginViewModel @AssistedInject constructor( } private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) { - if (isValidQrCode(action.qrCode)) { - setState { - copy( - connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice - ) + Timber.tag(TAG).d("Scanned code: ${action.qrCode}") + + val rendezvous = Rendezvous.buildChannelFromCode(action.qrCode) { reason -> + Timber.tag(TAG).d("Rendezvous cancelled: $reason") + onFailed(reason) + } + + setState { + copy( + connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice + ) + } + + _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) + + viewModelScope.launch(Dispatchers.IO) { + val confirmationCode = rendezvous.startAfterScanningCode() + Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") + confirmationCode ?.let { + onConnectionEstablished(it) } - _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) + rendezvous.completeOnNewDevice() } // TODO. UI test purpose. Fixme remove! diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt deleted file mode 100644 index d7d74b08e9..0000000000 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.voicebroadcast - -/** Voice Broadcast State Event. */ -const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" From ef574bd82fbc5e33f0d4da51958158de9773a45c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 12 Oct 2022 14:32:09 +0300 Subject: [PATCH 0034/1068] Merge branch 'develop' into feature/ons/qr_code_login_ui # Conflicts: # library/ui-strings/src/main/res/values/strings.xml # library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml # vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt # vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt # vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt # vector/src/main/java/im/vector/app/features/VectorFeatures.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt # vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt # vector/src/main/res/layout/fragment_other_sessions.xml # vector/src/main/res/layout/fragment_settings_devices.xml --- .../voicebroadcast/VoiceBroadcastConstants.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt new file mode 100644 index 0000000000..d7d74b08e9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.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 im.vector.app.features.voicebroadcast + +/** Voice Broadcast State Event. */ +const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" From b03240330d9905fc422a2fd3d166376c5d80f3a7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 12 Oct 2022 13:08:01 +0100 Subject: [PATCH 0035/1068] Only do completeOnNewDevice if we received a confirmation code --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 05344f0a67..877d32a11d 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -79,8 +79,8 @@ class QrCodeLoginViewModel @AssistedInject constructor( Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") confirmationCode ?.let { onConnectionEstablished(it) + rendezvous.completeOnNewDevice() } - rendezvous.completeOnNewDevice() } // TODO. UI test purpose. Fixme remove! From 1e60f3c25b457e2cb40e5c8770e673c25f2fa941 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:35:45 +0100 Subject: [PATCH 0036/1068] Make initialDeviceName optional --- .../org/matrix/android/sdk/api/auth/AuthenticationService.kt | 2 +- .../android/sdk/internal/auth/DefaultAuthenticationService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 6bb47dda5b..9fde6d9326 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -140,7 +140,7 @@ interface AuthenticationService { suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String, + initialDeviceName: String?, deviceId: String? = null ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 773f2118b1..7aeeacd10b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -425,7 +425,7 @@ internal class DefaultAuthenticationService @Inject constructor( override suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String, + initialDeviceName: String?, deviceId: String?, ): Session { return loginTokenAuthTask.execute( From e2f3dde5c1579c88521ae7b2e16b725f7e7c48e5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 15:58:19 +0100 Subject: [PATCH 0037/1068] Use correct var name --- .../android/sdk/internal/auth/DefaultAuthenticationService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 7aeeacd10b..5449c0a735 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -66,7 +66,7 @@ internal class DefaultAuthenticationService @Inject constructor( private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, private val directLoginTask: DirectLoginTask, - private val loginTokenAuthTask: QrLoginTokenTask + private val qrLoginTokenTask: QrLoginTokenTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -428,7 +428,7 @@ internal class DefaultAuthenticationService @Inject constructor( initialDeviceName: String?, deviceId: String?, ): Session { - return loginTokenAuthTask.execute( + return qrLoginTokenTask.execute( QrLoginTokenTask.Params( homeServerConnectionConfig = homeServerConnectionConfig, loginToken = loginToken, From ca7a6efadee7430c4ac431a7cb89733db7486cc6 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:02:57 +0100 Subject: [PATCH 0038/1068] Add missing binding --- .../java/org/matrix/android/sdk/internal/auth/AuthModule.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index 463692e574..b1f65194f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.auth.db.AuthRealmModule import org.matrix.android.sdk.internal.auth.db.RealmPendingSessionStore import org.matrix.android.sdk.internal.auth.db.RealmSessionParamsStore import org.matrix.android.sdk.internal.auth.login.DefaultDirectLoginTask +import org.matrix.android.sdk.internal.auth.login.DefaultQrLoginTokenTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthDatabase import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter @@ -94,4 +96,7 @@ internal abstract class AuthModule { @Binds abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService + + @Binds + abstract fun bindQrLoginTokenTask(task: DefaultQrLoginTokenTask): QrLoginTokenTask } From ac80ae5632c4543b199189b90662ed4c971373eb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:08:50 +0100 Subject: [PATCH 0039/1068] Set default value for optional params --- .../matrix/android/sdk/internal/auth/data/TokenLoginParams.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt index 22cc185fa7..52045a1d7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt @@ -24,6 +24,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes internal data class TokenLoginParams( @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN, @Json(name = "token") val token: String, - @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, - @Json(name = "device_id") override val deviceId: String? + @Json(name = "initial_device_display_name") override val deviceDisplayName: String? = null, + @Json(name = "device_id") override val deviceId: String? = null ) : LoginParams From bc0843eddf1712983dd4990ac6835dd68fc0bbb5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:11:41 +0100 Subject: [PATCH 0040/1068] Another default value fix --- .../org/matrix/android/sdk/api/auth/AuthenticationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 9fde6d9326..1cc53b311c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -140,7 +140,7 @@ interface AuthenticationService { suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, - initialDeviceName: String?, + initialDeviceName: String? = null, deviceId: String? = null ): Session } From 991eeb1de6c29515aa43d4c551b8cf12580f5f53 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 16:28:47 +0100 Subject: [PATCH 0041/1068] Map for soft logout --- .../im/vector/app/features/signout/soft/SoftLogoutController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt index b1a240e942..a1ed27df1d 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt @@ -152,6 +152,7 @@ class SoftLogoutController @Inject constructor( LoginType.SSO -> buildLoginSSOForm() LoginType.DIRECT, LoginType.CUSTOM, + LoginType.QR, LoginType.UNSUPPORTED -> buildLoginUnsupportedForm() LoginType.UNKNOWN -> Unit } From 9a72d6529b0a76bc83c28ed217f79c8a69c6acc4 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:08:45 +0100 Subject: [PATCH 0042/1068] Support for navigation to home screen --- .../im/vector/app/features/login/qr/QrCodeLoginActivity.kt | 7 +++++++ .../vector/app/features/login/qr/QrCodeLoginViewEvents.kt | 1 + 2 files changed, 8 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt index 042f885231..fac31ce4e3 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.features.home.HomeActivity import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber @@ -74,6 +75,7 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { when (it) { QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen() QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen() + QrCodeLoginViewEvents.NavigateToHomeScreen -> handleNavigateToHomeScreen() } } } @@ -94,6 +96,11 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { ) } + private fun handleNavigateToHomeScreen() { + val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true) + startActivity(intent) + } + companion object { private const val FRAGMENT_QR_CODE_INSTRUCTIONS_TAG = "FRAGMENT_QR_CODE_INSTRUCTIONS_TAG" diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt index dc258408e7..0f282fee38 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class QrCodeLoginViewEvents : VectorViewEvents { object NavigateToStatusScreen : QrCodeLoginViewEvents() object NavigateToShowQrCodeScreen : QrCodeLoginViewEvents() + object NavigateToHomeScreen : QrCodeLoginViewEvents() } From dd47297dfd4bc774c509d06ccc1316fbb70e6514 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:15:52 +0100 Subject: [PATCH 0043/1068] QR login + E2EE set up --- .../sdk/internal/rendezvous/Rendezvous.kt | 210 ++++++++++++++++++ .../features/login/qr/QrCodeLoginViewModel.kt | 62 +++--- 2 files changed, 246 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt new file mode 100644 index 0000000000..2f85a97c55 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt @@ -0,0 +1,210 @@ +/* + * 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.internal.rendezvous + +import android.net.Uri +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.util.MatrixJsonParser +import org.matrix.android.sdk.internal.rendezvous.channels.ECDHRendezvousChannel +import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransport +import timber.log.Timber + +internal enum class PayloadType(val value: String) { + @Json(name = "m.login.start") Start("m.login.start"), + @Json(name = "m.login.finish") Finish("m.login.finish"), + @Json(name = "m.login.progress") Progress("m.login.progress") +} + +@JsonClass(generateAdapter = true) +internal data class Payload( + @Json val type: PayloadType, + @Json val intent: RendezvousIntent? = null, + @Json val outcome: String? = null, + @Json val protocols: List? = null, + @Json val protocol: String? = null, + @Json val homeserver: String? = null, + @Json val login_token: String? = null, + @Json val device_id: String? = null, + @Json val device_key: String? = null, + @Json val verifying_device_id: String? = null, + @Json val verifying_device_key: String? = null, + @Json val master_key: String? = null +) + +private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value + +/** + * Implementation of MSC3906 to sign in + E2EE set up using a QR code. + */ +class Rendezvous( + val channel: RendezvousChannel, + val theirIntent: RendezvousIntent, +) { + companion object { + fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { + val parsed = MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) ?: throw RuntimeException("Invalid code") + + val transport = SimpleHttpRendezvousTransport(onCancelled, parsed.rendezvous.transport.uri) + + return Rendezvous( + ECDHRendezvousChannel(transport, parsed.rendezvous.key), + parsed.intent + ) + } + } + + private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java) + // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE + val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE + + private suspend fun areIntentsIncompatible(): Boolean { + val incompatible = theirIntent == ourIntent + + Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible") + + if (incompatible) { + send(Payload(PayloadType.Finish, intent = ourIntent)) + val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) RendezvousFailureReason.OtherDeviceNotSignedIn else RendezvousFailureReason.OtherDeviceAlreadySignedIn + channel.cancel(reason) + } + + return incompatible + } + + suspend fun startAfterScanningCode(): String? { + val checksum = channel.connect(); + + Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum") + + if (areIntentsIncompatible()) { + return null + } + + // get protocols + Timber.tag(TAG).i("Waiting for protocols"); + val protocolsResponse = receive() + + if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains("login_token")) { + send(Payload(PayloadType.Finish, outcome = "unsupported")) + Timber.tag(TAG).i("No supported protocol") + cancel(RendezvousFailureReason.Unknown) + return null + } + + send(Payload(PayloadType.Progress, protocol = "login_token")) + + return checksum + } + + suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { + Timber.tag(TAG).i("Waiting for login_token"); + + val loginToken = receive() + + if (loginToken?.type == PayloadType.Finish) { + when (loginToken.outcome) { + "declined" -> { + Timber.tag(TAG).i("Login declined by other device") + channel.cancel(RendezvousFailureReason.UserDeclined) + return null + } + "unsupported" -> { + Timber.tag(TAG).i("Not supported") + channel.cancel(RendezvousFailureReason.HomeserverLacksSupport) + return null + } + } + channel.cancel(RendezvousFailureReason.Unknown) + return null + } + + val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") + val login_token = loginToken.login_token ?: throw RuntimeException("No login token returned") + + Timber.tag(TAG).i("Got login_token: $login_token for $homeserver"); + + val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver)) + return authenticationService.loginUsingQrLoginToken(hsConfig, login_token) + } + + suspend fun completeVerificationOnNewDevice(session: Session) { + val userId = session.myUserId + val crypto = session.cryptoService() + val deviceId = crypto.getMyDevice().deviceId + val deviceKey = crypto.getMyDevice().fingerprint() + send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) + + // await confirmation of verification + + val verificationResponse = receive() + val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) { + Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") + return; + } + + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + // TODO: what do we do with the master key? +// verificationResponse.master_key ?.let { +// // set master key as trusted +// crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) +// } + + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + + session.sharedSecretStorageService() .let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + } + } + + private suspend fun receive(): Payload? { + val data = channel.receive()?: return null + return adapter.fromJson(data.toString(Charsets.UTF_8)) + } + + private suspend fun send(payload: Payload) { + channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8)); + } + + suspend fun cancel(reason: RendezvousFailureReason) { + channel.cancel(reason) + } + + suspend fun close() { + channel.close() + } +} diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 877d32a11d..d9c30690a7 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -16,19 +16,32 @@ package im.vector.app.features.login.qr +import android.content.Context import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.delay +import im.vector.app.features.home.HomeActivity +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.rendezvous.Rendezvous +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, + private val applicationContext: Context, + private val authenticationService: AuthenticationService, + private val activeSessionHolder: ActiveSessionHolder, ) : VectorViewModel(initialState) { + val TAG: String = QrCodeLoginViewModel::class.java.simpleName @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -75,28 +88,32 @@ class QrCodeLoginViewModel @AssistedInject constructor( _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) viewModelScope.launch(Dispatchers.IO) { - val confirmationCode = rendezvous.startAfterScanningCode() - Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") - confirmationCode ?.let { - onConnectionEstablished(it) - rendezvous.completeOnNewDevice() - } - } + try { + val confirmationCode = rendezvous.startAfterScanningCode() + Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") + confirmationCode?.let { + onConnectionEstablished(it) + val session = rendezvous.waitForLoginOnNewDevice(authenticationService) + onSigningIn() + session?.let { + activeSessionHolder.setActiveSession(session) + authenticationService.reset() - // TODO. UI test purpose. Fixme remove! - viewModelScope.launch { - delay(3000) - onFailed(QrCodeLoginErrorType.TIMEOUT, true) - delay(3000) - onConnectionEstablished("1234-ABCD-5678-EFGH") - delay(3000) - onSigningIn() - delay(3000) - onFailed(QrCodeLoginErrorType.DEVICE_IS_NOT_SUPPORTED, false) + session.configureAndStart(applicationContext) + + rendezvous.completeVerificationOnNewDevice(session) + + _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) + } + } + } catch (failure: Throwable) { + Timber.tag(TAG).e(failure, "Error occurred during sign in") + onFailed(RendezvousFailureReason.Unknown) + } } } - private fun onFailed(errorType: QrCodeLoginErrorType, canTryAgain: Boolean) { + private fun onFailed(reason: RendezvousFailureReason) { setState { copy( connectionStatus = QrCodeLoginConnectionStatus.Failed(errorType, canTryAgain) @@ -121,13 +138,6 @@ class QrCodeLoginViewModel @AssistedInject constructor( } } - /** - * TODO. UI test purpose. Fixme accordingly. - */ - private fun isValidQrCode(qrCode: String): Boolean { - return qrCode.startsWith("http") - } - /** * TODO. UI test purpose. Fixme accordingly. */ From 7bc0bd3b57d1db71742051fe65d6b1003ad2037e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:26:22 +0100 Subject: [PATCH 0044/1068] Reduce logging --- .../channels/ECDHRendezvousChannel.kt | 218 ++++++++++++++++++ .../SimpleHttpRendezvousTransport.kt | 185 +++++++++++++++ 2 files changed, 403 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt new file mode 100644 index 0000000000..cced29aab4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt @@ -0,0 +1,218 @@ +/* + * 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.internal.rendezvous.channels + +import android.util.Base64 +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import okhttp3.MediaType.Companion.toMediaType +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.util.MatrixJsonParser +import org.matrix.android.sdk.internal.extensions.toUnsignedInt +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.internal.rendezvous.RendezvousChannel +import org.matrix.android.sdk.internal.rendezvous.RendezvousTransport +import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvous +import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousError +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.internal.rendezvous.model.SecureRendezvousChannelAlgorithm +import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransportDetails +import org.matrix.olm.OlmSAS +import timber.log.Timber +import java.security.SecureRandom +import java.util.LinkedList +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +@JsonClass(generateAdapter = true) +data class ECDHPayload( + @Json val algorithm: SecureRendezvousChannelAlgorithm? = null, + @Json val key: String? = null, + @Json val ciphertext: String? = null, + @Json val iv: String? = null +) + +private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value + +fun getDecimalCodeRepresentation(byteArray: ByteArray): String { + val b0 = byteArray[0].toUnsignedInt() // need unsigned byte + val b1 = byteArray[1].toUnsignedInt() // need unsigned byte + val b2 = byteArray[2].toUnsignedInt() // need unsigned byte + val b3 = byteArray[3].toUnsignedInt() // need unsigned byte + val b4 = byteArray[4].toUnsignedInt() // need unsigned byte + // (B0 << 5 | B1 >> 3) + 1000 + val first = (b0.shl(5) or b1.shr(3)) + 1000 + // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 + val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 + // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 + val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 + return "$first-$second-$third" +} + +const val ALGORITHM_SPEC = "AES/GCM/NoPadding" +const val KEY_SPEC = "AES" + +/** + * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903: + * https://github.com/matrix-org/matrix-spec-proposals/pull/3903 + */ +class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?): RendezvousChannel { + private var olmSAS: OlmSAS? + private val ourPublicKey: ByteArray + private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) + private var theirPublicKey: ByteArray? = null + private var aesKey: ByteArray? = null + + init { + theirPublicKeyBase64 ?.let { + theirPublicKey = Base64.decode(it, Base64.NO_WRAP) + } + olmSAS = OlmSAS() + ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP) + } + + override suspend fun connect(): String { + if (olmSAS == null) { + throw RuntimeException("Channel closed") + } + val isInitiator = theirPublicKey == null + + if (isInitiator) { +// Timber.tag(TAG).i("Waiting for other device to send their public key") + val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") + + if (res.key == null) { + throw RendezvousError( + "Unsupported algorithm: ${res.algorithm}", + RendezvousFailureReason.UnsupportedAlgorithm, + ) + } + theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) + } else { + // send our public key unencrypted +// Timber.tag(TAG).i("Sending public key") + send(ECDHPayload( + algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, + key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) + )) + } + + olmSAS!!.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + + val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) + val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) + val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" + + aesKey = olmSAS!!.generateShortCode(aesInfo, 32) + +// Timber.tag(TAG).i("Our public key: ${Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)}") +// Timber.tag(TAG).i("Their public key: ${Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)}") +// Timber.tag(TAG).i("AES info: $aesInfo") +// Timber.tag(TAG).i("AES key: ${Base64.encodeToString(aesKey, Base64.NO_WRAP)}") + + val rawChecksum = olmSAS!!.generateShortCode(aesInfo, 5) + return getDecimalCodeRepresentation(rawChecksum) + } + + private suspend fun send(payload: ECDHPayload) { + transport.send("application/json".toMediaType(), ecdhAdapter.toJson(payload).toByteArray(Charsets.UTF_8)) + } + + override suspend fun send(data: ByteArray) { + if (aesKey == null) { + throw RuntimeException("Shared secret not established") + } + send(encrypt(data)) + } + + private suspend fun receiveAsPayload(): ECDHPayload? { + transport.receive()?.toString(Charsets.UTF_8) ?.let { + return ecdhAdapter.fromJson(it) + } ?: return null + } + + override suspend fun receive(): ByteArray? { + if (aesKey == null) { + throw RuntimeException("Shared secret not established") + } + val payload = receiveAsPayload() ?: return null + return decrypt(payload) + } + + override suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode { + return ECDHRendezvousCode( + intent, + rendezvous = ECDHRendezvous( + transport.details() as SimpleHttpRendezvousTransportDetails, + SecureRendezvousChannelAlgorithm.ECDH_V1, + key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) + ) + ) + } + + override suspend fun cancel(reason: RendezvousFailureReason) { + try { + transport.cancel(reason) + } finally { + close() + } + } + + override suspend fun close() { + olmSAS?.releaseSas() + olmSAS = null + } + + private fun encrypt(plainText: ByteArray): ECDHPayload { +// Timber.tag(TAG).d("Encrypting: ${plainText.toString(Charsets.UTF_8)}") + val iv = ByteArray(16) + SecureRandom().nextBytes(iv) + + val cipherText = LinkedList() + + val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC) + val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC) + val ivParameterSpec = IvParameterSpec(iv) + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) + cipherText.addAll(encryptCipher.update(plainText).toList()) + cipherText.addAll(encryptCipher.doFinal().toList()) + + return ECDHPayload( + ciphertext = Base64.encodeToString(cipherText.toByteArray(), Base64.NO_WRAP), + iv = Base64.encodeToString(iv, Base64.NO_WRAP) + ) + } + + private fun decrypt(payload: ECDHPayload): ByteArray { + val iv = Base64.decode(payload.iv, Base64.NO_WRAP) + val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC) + val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC) + val ivParameterSpec = IvParameterSpec(iv) + encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + + val plainText = LinkedList() + plainText.addAll(encryptCipher.update(Base64.decode(payload.ciphertext, Base64.NO_WRAP)).toList()) + plainText.addAll(encryptCipher.doFinal().toList()) + + val plainTextBytes = plainText.toByteArray() + +// Timber.tag(TAG).d("Decrypted: ${plainTextBytes.toString(Charsets.UTF_8)}") + return plainTextBytes + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt new file mode 100644 index 0000000000..cc4346d55e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -0,0 +1,185 @@ +/* + * 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.internal.rendezvous.transports + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import kotlinx.coroutines.delay +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.internal.rendezvous.RendezvousTransport +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportDetails +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportType +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Date + +private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value + +@JsonClass(generateAdapter = true) +data class SimpleHttpRendezvousTransportDetails( + @Json val uri: String +): RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) + +/** + * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 + */ +class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?, rendezvousUri: String?) : RendezvousTransport { + override var ready = false + private var cancelled = false + private var uri: String? + private var etag: String? = null + private var expiresAt: Date? = null + + init { + uri = rendezvousUri + } + + override suspend fun details(): RendezvousTransportDetails { + val uri = uri ?: throw IllegalStateException("Rendezvous not set up") + + return SimpleHttpRendezvousTransportDetails(uri) + } + + override suspend fun send(contentType: MediaType, data: ByteArray) { + if (cancelled) { + return + } + + val method = if (uri != null) "PUT" else "POST" + // TODO: properly determine endpoint + val uri = if (uri != null) uri!! else "https://rendezvous.lab.element.dev" + +// Timber.tag(TAG).i("Sending data: ${data.toString(Charsets.UTF_8)} to $uri") + + val httpClient = okhttp3.OkHttpClient.Builder().build() + + val request = Request.Builder() + .url(uri) + .method(method, data.toRequestBody()) + .header("content-type", contentType.toString()) + + etag ?.let { + request.header("if-match", it) + } + + val response = httpClient.newCall(request.build()).execute() + + if (response.code == 404) { + cancel(RendezvousFailureReason.Unknown) + } + etag = response.header("etag") + + Timber.tag(TAG).i("Sent data to $uri new etag $etag") + + if (method == "POST") { + val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response") + + response.header("expires") ?.let { + val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz") + expiresAt = format.parse(it) + } + + // resolve location header which could be relative or absolute + this.uri = response.request.url.toUri().resolve(location).toString() + ready = true + } + } + + override suspend fun receive(): ByteArray? { + val uri = uri ?: throw IllegalStateException("Rendezvous not set up") + var done = false + val httpClient = okhttp3.OkHttpClient.Builder().build() + while (!done) { + if (cancelled) { + return null + } + Timber.tag(TAG).i("Polling: $uri after etag $etag") + val request = Request.Builder() + .url(uri) + .get() + + etag ?.let { + request.header("if-none-match", it) + } + + val response = httpClient.newCall(request.build()).execute() + + try { +// Timber.tag(TAG).d("Received polling response: ${response.code} from $uri") + + if (response.code == 404) { + cancel(RendezvousFailureReason.Unknown) + return null + } + + // rely on server expiring the channel rather than checking ourselves + + if (response.header("content-type") != "application/json") { + response.header("etag")?.let { + etag = it + } + } else if (response.code == 200) { + response.header("etag")?.let { + etag = it + } + val data = response.body?.bytes() +// Timber.tag(TAG).d("Received data: ${data?.toString(Charsets.UTF_8)} from $uri with etag $etag") + return data + } + + done = false + delay(1000) + } finally { + response.close() + } + } + + return null + } + + override suspend fun cancel(reason: RendezvousFailureReason) { + var mappedReason = reason + Timber.tag(TAG).i("$expiresAt") + if (mappedReason == RendezvousFailureReason.Unknown && + expiresAt != null && Date() > expiresAt) { + mappedReason = RendezvousFailureReason.Expired + } + + cancelled = true + ready = false + onCancelled ?.let { it(mappedReason) } + + if (mappedReason == RendezvousFailureReason.UserDeclined) { + uri ?.let { + try { + val httpClient = okhttp3.OkHttpClient.Builder().build() + val request = Request.Builder() + .url(it) + .delete() + .build() + httpClient.newCall(request).execute() + } catch (e: Exception) { + Timber.tag(TAG).w(e, "Failed to delete channel") + } + } + } + } +} From 6399032312c743793fc79ec1f7863c95aec0db1a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 22:23:26 +0100 Subject: [PATCH 0045/1068] Fix bad merge --- .../org/matrix/android/sdk/api/auth/AuthenticationService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 1cc53b311c..252c33a8c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -130,13 +130,14 @@ interface AuthenticationService { * Return true if qr code login is supported by the server, false otherwise. */ suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean + + /** * Authenticate using m.login.token method during sign in with QR code. * @param homeServerConnectionConfig the information about the homeserver and other configuration * @param loginToken the m.login.token * @param initialDeviceName the initial device name * @param deviceId the device id, optional. If not provided or null, the server will generate one. */ - suspend fun loginUsingQrLoginToken( homeServerConnectionConfig: HomeServerConnectionConfig, loginToken: String, From 958ee2d3561fb4b871ba83c6cb3be2ce4fbeb943 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 22:32:02 +0100 Subject: [PATCH 0046/1068] Revert "Revert "Retry scanning if not a QR code"" This reverts commit 9429a4f22aabfbff23ffc223c3b090626be553dc. --- .../app/features/login/qr/QrCodeLoginInstructionsFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt index ae3ba9574b..17b4ff2409 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt @@ -83,6 +83,7 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment Date: Thu, 13 Oct 2022 22:37:19 +0100 Subject: [PATCH 0047/1068] Revert "Revert "Implementations of MSC3886 and MSC3903"" This reverts commit 489dfd73546a78246b1f18faf97e7a36bb4e0241. --- .../android/sdk/api/logger/LoggerTag.kt | 1 + .../internal/rendezvous/RendezvousChannel.kt | 45 +++++++++++++++++++ .../rendezvous/RendezvousFailureReason.kt | 31 +++++++++++++ .../rendezvous/RendezvousTransport.kt | 29 ++++++++++++ .../rendezvous/model/ECDHRendezvous.kt | 34 ++++++++++++++ .../rendezvous/model/EmbeddedRendezvous.kt | 26 +++++++++++ .../rendezvous/model/RendezvousError.kt | 22 +++++++++ .../rendezvous/model/RendezvousIntent.kt | 24 ++++++++++ .../model/RendezvousTransportDetails.kt | 25 +++++++++++ .../model/RendezvousTransportType.kt | 23 ++++++++++ .../model/SecureRendezvousChannelAlgorithm.kt | 23 ++++++++++ 11 files changed, 283 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt index ae65963f37..22af8cebbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt @@ -27,6 +27,7 @@ open class LoggerTag(name: String, parentTag: LoggerTag? = null) { object SYNC : LoggerTag("SYNC") object VOIP : LoggerTag("VOIP") object CRYPTO : LoggerTag("CRYPTO") + object RENDEZVOUS : LoggerTag("RZ") val value: String = if (parentTag == null) { name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt new file mode 100644 index 0000000000..43552f46be --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt @@ -0,0 +1,45 @@ +/* + * 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.internal.rendezvous + +import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent + +interface RendezvousChannel { + var transport: RendezvousTransport; + /** + * @returns the checksum/confirmation digits to be shown to the user + */ + suspend fun connect(): String + /** + * Send a payload via the channel. + * @param data payload to send + */ + suspend fun send(data: ByteArray) + /** + * Receive a payload from the channel. + * @returns the received payload + */ + suspend fun receive(): ByteArray? + /** + * @returns a representation of the channel that can be encoded in a QR or similar + */ + suspend fun close() + // TODO: this should be transport independent in the future + suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode + suspend fun cancel(reason: RendezvousFailureReason) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt new file mode 100644 index 0000000000..0e2ea8c758 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt @@ -0,0 +1,31 @@ +/* + * 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.internal.rendezvous + +enum class RendezvousFailureReason(val value: String, val canRetry: Boolean = true) { + UserDeclined("user_declined"), + OtherDeviceNotSignedIn("other_device_not_signed_in"), + OtherDeviceAlreadySignedIn("other_device_already_signed_in"), + Unknown("unknown"), + Expired("expired"), + UserCancelled("user_cancelled"), + InvalidCode("invalid_code"), + UnsupportedAlgorithm("unsupported_algorithm", false), + DataMismatch("data_mismatch"), + UnsupportedTransport("unsupported_transport", false), + HomeserverLacksSupport("homeserver_lacks_support", false) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt new file mode 100644 index 0000000000..753b0bc6fa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt @@ -0,0 +1,29 @@ +/* + * 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.internal.rendezvous + +import okhttp3.MediaType +import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportDetails + +interface RendezvousTransport { + var ready: Boolean; + var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?; + suspend fun details(): RendezvousTransportDetails; + suspend fun send(contentType: MediaType, data: ByteArray); + suspend fun receive(): ByteArray?; + suspend fun cancel(reason: RendezvousFailureReason); +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt new file mode 100644 index 0000000000..e296dce09d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt @@ -0,0 +1,34 @@ +/* + * 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.internal.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransportDetails + +@JsonClass(generateAdapter = true) +data class ECDHRendezvous( + @Json val transport: SimpleHttpRendezvousTransportDetails, + @Json val algorithm: SecureRendezvousChannelAlgorithm, + @Json val key: String +) + +@JsonClass(generateAdapter = true) +data class ECDHRendezvousCode( + @Json val intent: RendezvousIntent, + @Json val rendezvous: ECDHRendezvous +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt new file mode 100644 index 0000000000..d490de0133 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.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 org.matrix.android.sdk.internal.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +open class EmbeddedRendezvous( + @Json(name = "transport") val transport: RendezvousTransportDetails, + @Json(name = "algorithm") val algorithm: SecureRendezvousChannelAlgorithm +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt new file mode 100644 index 0000000000..ead273e8ce --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.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.internal.rendezvous.model + +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason + +class RendezvousError(val description: String, val reason: RendezvousFailureReason): RuntimeException(description) { +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt new file mode 100644 index 0000000000..6285c1e57a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.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 org.matrix.android.sdk.internal.rendezvous.model + +import com.squareup.moshi.Json + +enum class RendezvousIntent { + @Json(name = "login.start") LOGIN_ON_NEW_DEVICE, + @Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt new file mode 100644 index 0000000000..1b1826194f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt @@ -0,0 +1,25 @@ +/* + * 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.internal.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +open class RendezvousTransportDetails( + @Json val type: RendezvousTransportType +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt new file mode 100644 index 0000000000..c3b6ba7ac8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt @@ -0,0 +1,23 @@ +/* + * 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.internal.rendezvous.model + +import com.squareup.moshi.Json + +enum class RendezvousTransportType(val value: String) { + @Json(name = "http.v1") MSC3886_SIMPLE_HTTP_V1("http.v1") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt new file mode 100644 index 0000000000..ddc0ae20e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -0,0 +1,23 @@ +/* + * 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.internal.rendezvous.model + +import com.squareup.moshi.Json + +enum class SecureRendezvousChannelAlgorithm(val value: String) { + @Json(name = "m.rendezvous.v1.curve25519-aes-sha256") ECDH_V1("m.rendezvous.v1.curve25519-aes-sha256") +} From f04f0e6fac25345b7cdc39181e0ac4743fdb37c5 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 22:42:58 +0100 Subject: [PATCH 0048/1068] Revert "Revert "Partial implementation of QR login logic"" This reverts commit e305478ddabf39c6e2a272485a1b154b3ad12263. --- .../src/main/res/values/strings.xml | 1 + .../login/qr/QrCodeLoginConnectionStatus.kt | 4 +++- .../features/login/qr/QrCodeLoginErrorType.kt | 23 ------------------- .../login/qr/QrCodeLoginStatusFragment.kt | 12 ++++++---- .../features/login/qr/QrCodeLoginViewModel.kt | 4 ++-- 5 files changed, 13 insertions(+), 31 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginErrorType.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 822e9d3865..108fe7db7c 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3368,6 +3368,7 @@ Linking with this device is not supported. The linking wasn’t completed in the required time. The request was denied on the other device. + The request failed. Open ${app_name} on your other device Go to Settings -> Security & Privacy -> Show All Sessions Select \'Show QR code\' diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt index 330562b874..4de191f863 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt @@ -16,9 +16,11 @@ package im.vector.app.features.login.qr +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason + sealed class QrCodeLoginConnectionStatus { object ConnectingToDevice : QrCodeLoginConnectionStatus() data class Connected(val securityCode: String, val canConfirmSecurityCode: Boolean) : QrCodeLoginConnectionStatus() object SigningIn : QrCodeLoginConnectionStatus() - data class Failed(val errorType: QrCodeLoginErrorType, val canTryAgain: Boolean) : QrCodeLoginConnectionStatus() + data class Failed(val errorType: RendezvousFailureReason, val canTryAgain: Boolean) : QrCodeLoginConnectionStatus() } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginErrorType.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginErrorType.kt deleted file mode 100644 index 9a6cc13de0..0000000000 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginErrorType.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.login.qr - -enum class QrCodeLoginErrorType { - DEVICE_IS_NOT_SUPPORTED, - TIMEOUT, - REQUEST_WAS_DENIED, -} diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 1c0841aa11..fb372cbb2f 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -27,6 +27,7 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason @AndroidEntryPoint class QrCodeLoginStatusFragment : VectorBaseFragment() { @@ -77,11 +78,12 @@ class QrCodeLoginStatusFragment : VectorBaseFragment getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) - QrCodeLoginErrorType.TIMEOUT -> getString(R.string.qr_code_login_header_failed_timeout_description) - QrCodeLoginErrorType.REQUEST_WAS_DENIED -> getString(R.string.qr_code_login_header_failed_denied_description) + private fun getErrorCode(reason: RendezvousFailureReason): String { + return when (reason) { + RendezvousFailureReason.UnsupportedAlgorithm, RendezvousFailureReason.UnsupportedTransport -> getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) + RendezvousFailureReason.Expired -> getString(R.string.qr_code_login_header_failed_timeout_description) + RendezvousFailureReason.UserDeclined -> getString(R.string.qr_code_login_header_failed_denied_description) + else -> getString(R.string.qr_code_login_header_failed_other_description) } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index d9c30690a7..8461d8d88f 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -39,7 +39,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, private val applicationContext: Context, private val authenticationService: AuthenticationService, - private val activeSessionHolder: ActiveSessionHolder, + private val activeSessionHolder: ActiveSessionHolder ) : VectorViewModel(initialState) { val TAG: String = QrCodeLoginViewModel::class.java.simpleName @@ -116,7 +116,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( private fun onFailed(reason: RendezvousFailureReason) { setState { copy( - connectionStatus = QrCodeLoginConnectionStatus.Failed(errorType, canTryAgain) + connectionStatus = QrCodeLoginConnectionStatus.Failed(reason, reason.canRetry) ) } } From f2d76be20c724f461d4b4c86bba767f52857a553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 23:06:43 +0000 Subject: [PATCH 0049/1068] Bump com.google.devtools.ksp from 1.7.20-1.0.6 to 1.7.20-1.0.7 Bumps [com.google.devtools.ksp](https://github.com/google/ksp) from 1.7.20-1.0.6 to 1.7.20-1.0.7. - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/compare/1.7.20-1.0.6...1.7.20-1.0.7) --- updated-dependencies: - dependency-name: com.google.devtools.ksp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e474b68531..32a7c2c26f 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ plugins { // Detekt id "io.gitlab.arturbosch.detekt" version "1.21.0" // Ksp - id "com.google.devtools.ksp" version "1.7.20-1.0.6" + id "com.google.devtools.ksp" version "1.7.20-1.0.7" // Dependency Analysis id 'com.autonomousapps.dependency-analysis' version "1.13.1" From 5abb786b6b792ea70f38ca616bd5925a329af719 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 00:59:31 +0100 Subject: [PATCH 0050/1068] Fix copyright on SDK --- .../org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt | 2 +- .../matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt | 2 +- .../android/sdk/internal/rendezvous/RendezvousFailureReason.kt | 2 +- .../android/sdk/internal/rendezvous/RendezvousTransport.kt | 2 +- .../sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt | 2 +- .../android/sdk/internal/rendezvous/model/ECDHRendezvous.kt | 2 +- .../android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt | 2 +- .../android/sdk/internal/rendezvous/model/RendezvousError.kt | 2 +- .../android/sdk/internal/rendezvous/model/RendezvousIntent.kt | 2 +- .../sdk/internal/rendezvous/model/RendezvousTransportDetails.kt | 2 +- .../sdk/internal/rendezvous/model/RendezvousTransportType.kt | 2 +- .../rendezvous/model/SecureRendezvousChannelAlgorithm.kt | 2 +- .../rendezvous/transports/SimpleHttpRendezvousTransport.kt | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt index 2f85a97c55..d8fd323056 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt index 43552f46be..6655468808 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt index 0e2ea8c758..0920bf224f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt index 753b0bc6fa..f054b7cb73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt index cced29aab4..9f347b8536 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt index e296dce09d..14976ec836 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt index d490de0133..5a609d497e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt index ead273e8ce..feb7d0cf35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt index 6285c1e57a..d317531835 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt index 1b1826194f..ee9058daa3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt index c3b6ba7ac8..51371ddb2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt index ddc0ae20e7..122b6ede82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -1,5 +1,5 @@ /* - * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt index cc4346d55e..7918946d75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -1,5 +1,5 @@ /* - * 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. From c18439f99b7056619fcf1d7f7df3697d1ee5b63c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 01:07:19 +0100 Subject: [PATCH 0051/1068] Refactor code into api from internal --- .../rendezvous/Rendezvous.kt | 12 ++++---- .../rendezvous/RendezvousChannel.kt | 8 +++--- .../rendezvous/RendezvousFailureReason.kt | 4 +-- .../rendezvous/RendezvousTransport.kt | 6 ++-- .../channels/ECDHRendezvousChannel.kt | 28 ++++++++++--------- .../rendezvous/model/ECDHRendezvous.kt | 6 ++-- .../rendezvous/model/EmbeddedRendezvous.kt | 8 +++--- .../rendezvous/model/RendezvousError.kt | 6 ++-- .../rendezvous/model/RendezvousIntent.kt | 4 +-- .../model/RendezvousTransportDetails.kt | 4 +-- .../model/RendezvousTransportType.kt | 4 +-- .../model/SecureRendezvousChannelAlgorithm.kt | 4 +-- .../SimpleHttpRendezvousTransport.kt | 12 ++++---- .../login/qr/QrCodeLoginConnectionStatus.kt | 2 +- .../login/qr/QrCodeLoginStatusFragment.kt | 2 +- .../features/login/qr/QrCodeLoginViewModel.kt | 4 +-- 16 files changed, 58 insertions(+), 56 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/Rendezvous.kt (95%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/RendezvousChannel.kt (84%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/RendezvousFailureReason.kt (91%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/RendezvousTransport.kt (83%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/channels/ECDHRendezvousChannel.kt (90%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/ECDHRendezvous.kt (83%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/EmbeddedRendezvous.kt (72%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/RendezvousError.kt (79%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/RendezvousIntent.kt (87%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/RendezvousTransportDetails.kt (87%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/RendezvousTransportType.kt (86%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/model/SecureRendezvousChannelAlgorithm.kt (87%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/rendezvous/transports/SimpleHttpRendezvousTransport.kt (93%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index d8fd323056..534d5df1ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous +package org.matrix.android.sdk.api.rendezvous import android.net.Uri import com.squareup.moshi.Json @@ -29,10 +29,10 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.util.MatrixJsonParser -import org.matrix.android.sdk.internal.rendezvous.channels.ECDHRendezvousChannel -import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent -import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransport +import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import timber.log.Timber internal enum class PayloadType(val value: String) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt similarity index 84% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 6655468808..2a73e8f112 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.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. @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous +package org.matrix.android.sdk.api.rendezvous -import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent interface RendezvousChannel { var transport: RendezvousTransport; diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt index 0920bf224f..a607dc7f38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousFailureReason.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous +package org.matrix.android.sdk.api.rendezvous enum class RendezvousFailureReason(val value: String, val canRetry: Boolean = true) { UserDeclined("user_declined"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt similarity index 83% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt index f054b7cb73..11471288f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/RendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.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. @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous +package org.matrix.android.sdk.api.rendezvous import okhttp3.MediaType -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails interface RendezvousTransport { var ready: Boolean; diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 9f347b8536..0f69bd7eda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.channels +package org.matrix.android.sdk.api.rendezvous.channels import android.util.Base64 import com.squareup.moshi.Json @@ -23,15 +23,15 @@ import okhttp3.MediaType.Companion.toMediaType import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.internal.extensions.toUnsignedInt -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason -import org.matrix.android.sdk.internal.rendezvous.RendezvousChannel -import org.matrix.android.sdk.internal.rendezvous.RendezvousTransport -import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvous -import org.matrix.android.sdk.internal.rendezvous.model.ECDHRendezvousCode -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousError -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousIntent -import org.matrix.android.sdk.internal.rendezvous.model.SecureRendezvousChannelAlgorithm -import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousChannel +import org.matrix.android.sdk.api.rendezvous.RendezvousTransport +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvous +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm +import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransportDetails import org.matrix.olm.OlmSAS import timber.log.Timber import java.security.SecureRandom @@ -107,10 +107,12 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } else { // send our public key unencrypted // Timber.tag(TAG).i("Sending public key") - send(ECDHPayload( + send( + ECDHPayload( algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) - )) + ) + ) } olmSAS!!.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt similarity index 83% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt index 14976ec836..b203101b66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/ECDHRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.rendezvous.transports.SimpleHttpRendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransportDetails @JsonClass(generateAdapter = true) data class ECDHRendezvous( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt similarity index 72% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt index 5a609d497e..785ce1fed7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/EmbeddedRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.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. @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) open class EmbeddedRendezvous( - @Json(name = "transport") val transport: RendezvousTransportDetails, - @Json(name = "algorithm") val algorithm: SecureRendezvousChannelAlgorithm + @Json(name = "transport") val transport: RendezvousTransportDetails, + @Json(name = "algorithm") val algorithm: SecureRendezvousChannelAlgorithm ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt similarity index 79% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt index feb7d0cf35..f731c89649 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason class RendezvousError(val description: String, val reason: RendezvousFailureReason): RuntimeException(description) { } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt index d317531835..1c070599b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousIntent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt index ee9058daa3..55b3bbb5d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt index 51371ddb2f..9c3e44f25b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/RendezvousTransportType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt index 122b6ede82..9a9db58a41 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/model/SecureRendezvousChannelAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.model +package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 7918946d75..ba60459544 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.rendezvous.transports +package org.matrix.android.sdk.api.rendezvous.transports import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -23,10 +23,10 @@ import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason -import org.matrix.android.sdk.internal.rendezvous.RendezvousTransport -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportDetails -import org.matrix.android.sdk.internal.rendezvous.model.RendezvousTransportType +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousTransport +import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportType import timber.log.Timber import java.text.SimpleDateFormat import java.util.Date diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt index 4de191f863..4bef41b6c1 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt @@ -16,7 +16,7 @@ package im.vector.app.features.login.qr -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason sealed class QrCodeLoginConnectionStatus { object ConnectingToDevice : QrCodeLoginConnectionStatus() diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index fb372cbb2f..5451a16b44 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -27,7 +27,7 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding import im.vector.app.features.themes.ThemeUtils -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason @AndroidEntryPoint class QrCodeLoginStatusFragment : VectorBaseFragment() { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 8461d8d88f..276cedad43 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -31,8 +31,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.rendezvous.Rendezvous -import org.matrix.android.sdk.internal.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.Rendezvous +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( From c00ce91214cea1a0308d7885d46b5ea5224f5aea Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 01:11:25 +0100 Subject: [PATCH 0052/1068] Linting --- .../android/sdk/api/rendezvous/Rendezvous.kt | 27 ++++++++++--------- .../sdk/api/rendezvous/RendezvousChannel.kt | 7 ++++- .../sdk/api/rendezvous/RendezvousTransport.kt | 12 ++++----- .../channels/ECDHRendezvousChannel.kt | 9 +++---- .../api/rendezvous/model/RendezvousError.kt | 3 +-- .../SimpleHttpRendezvousTransport.kt | 2 +- .../features/login/qr/QrCodeLoginViewModel.kt | 2 -- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 534d5df1ca..e33130e529 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -22,6 +22,10 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -29,10 +33,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.util.MatrixJsonParser -import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel -import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode -import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent -import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import timber.log.Timber internal enum class PayloadType(val value: String) { @@ -80,6 +80,7 @@ class Rendezvous( } private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java) + // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE @@ -98,7 +99,7 @@ class Rendezvous( } suspend fun startAfterScanningCode(): String? { - val checksum = channel.connect(); + val checksum = channel.connect() Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum") @@ -107,7 +108,7 @@ class Rendezvous( } // get protocols - Timber.tag(TAG).i("Waiting for protocols"); + Timber.tag(TAG).i("Waiting for protocols") val protocolsResponse = receive() if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains("login_token")) { @@ -123,7 +124,7 @@ class Rendezvous( } suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { - Timber.tag(TAG).i("Waiting for login_token"); + Timber.tag(TAG).i("Waiting for login_token") val loginToken = receive() @@ -147,7 +148,7 @@ class Rendezvous( val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") val login_token = loginToken.login_token ?: throw RuntimeException("No login token returned") - Timber.tag(TAG).i("Got login_token: $login_token for $homeserver"); + Timber.tag(TAG).i("Got login_token: $login_token for $homeserver") val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver)) return authenticationService.loginUsingQrLoginToken(hsConfig, login_token) @@ -167,11 +168,11 @@ class Rendezvous( val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) { Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") - return; + return } // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified"); + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) // TODO: what do we do with the master key? @@ -183,7 +184,7 @@ class Rendezvous( // request secrets from the verifying device Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") - session.sharedSecretStorageService() .let { + session.sharedSecretStorageService().let { it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) @@ -192,12 +193,12 @@ class Rendezvous( } private suspend fun receive(): Payload? { - val data = channel.receive()?: return null + val data = channel.receive() ?: return null return adapter.fromJson(data.toString(Charsets.UTF_8)) } private suspend fun send(payload: Payload) { - channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8)); + channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8)) } suspend fun cancel(reason: RendezvousFailureReason) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 2a73e8f112..588d034f10 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -20,25 +20,30 @@ import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent interface RendezvousChannel { - var transport: RendezvousTransport; + var transport: RendezvousTransport + /** * @returns the checksum/confirmation digits to be shown to the user */ suspend fun connect(): String + /** * Send a payload via the channel. * @param data payload to send */ suspend fun send(data: ByteArray) + /** * Receive a payload from the channel. * @returns the received payload */ suspend fun receive(): ByteArray? + /** * @returns a representation of the channel that can be encoded in a QR or similar */ suspend fun close() + // TODO: this should be transport independent in the future suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode suspend fun cancel(reason: RendezvousFailureReason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt index 11471288f6..de0aed7efc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt @@ -20,10 +20,10 @@ import okhttp3.MediaType import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails interface RendezvousTransport { - var ready: Boolean; - var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?; - suspend fun details(): RendezvousTransportDetails; - suspend fun send(contentType: MediaType, data: ByteArray); - suspend fun receive(): ByteArray?; - suspend fun cancel(reason: RendezvousFailureReason); + var ready: Boolean + var onCancelled: ((reason: RendezvousFailureReason) -> Unit)? + suspend fun details(): RendezvousTransportDetails + suspend fun send(contentType: MediaType, data: ByteArray) + suspend fun receive(): ByteArray? + suspend fun cancel(reason: RendezvousFailureReason) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 0f69bd7eda..1c8bca5d1c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -21,10 +21,8 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import okhttp3.MediaType.Companion.toMediaType import org.matrix.android.sdk.api.logger.LoggerTag -import org.matrix.android.sdk.api.util.MatrixJsonParser -import org.matrix.android.sdk.internal.extensions.toUnsignedInt -import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.api.rendezvous.RendezvousChannel +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.api.rendezvous.RendezvousTransport import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvous import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode @@ -32,8 +30,9 @@ import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransportDetails +import org.matrix.android.sdk.api.util.MatrixJsonParser +import org.matrix.android.sdk.internal.extensions.toUnsignedInt import org.matrix.olm.OlmSAS -import timber.log.Timber import java.security.SecureRandom import java.util.LinkedList import javax.crypto.Cipher @@ -72,7 +71,7 @@ const val KEY_SPEC = "AES" * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903: * https://github.com/matrix-org/matrix-spec-proposals/pull/3903 */ -class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?): RendezvousChannel { +class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?) : RendezvousChannel { private var olmSAS: OlmSAS? private val ourPublicKey: ByteArray private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt index f731c89649..fec55ffb67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt @@ -18,5 +18,4 @@ package org.matrix.android.sdk.api.rendezvous.model import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason -class RendezvousError(val description: String, val reason: RendezvousFailureReason): RuntimeException(description) { -} +class RendezvousError(val description: String, val reason: RendezvousFailureReason) : RuntimeException(description) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index ba60459544..475a4fbe6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -36,7 +36,7 @@ private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName @JsonClass(generateAdapter = true) data class SimpleHttpRendezvousTransportDetails( @Json val uri: String -): RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) +) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) /** * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 276cedad43..7a51098439 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -26,11 +26,9 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.home.HomeActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.rendezvous.Rendezvous import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import timber.log.Timber From efa70fa0ff92b56d15f638dbd240a050f9b3ff25 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 13 Oct 2022 21:48:33 +0100 Subject: [PATCH 0053/1068] Revert "Retry scanning if not a QR code" This reverts commit 87956e943897333f3f7e9be9a6e59d9a7f0c4547. --- .../app/features/login/qr/QrCodeLoginInstructionsFragment.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt index 17b4ff2409..ae3ba9574b 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt @@ -83,7 +83,6 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment Date: Fri, 14 Oct 2022 01:45:03 +0100 Subject: [PATCH 0054/1068] Add flag to allow QR login on all servers + split flag for showing in device manager --- .../debug/features/DebugFeaturesStateFactory.kt | 10 ++++++++++ .../app/features/debug/features/DebugVectorFeatures.kt | 8 ++++++++ .../main/java/im/vector/app/features/VectorFeatures.kt | 4 ++++ .../app/features/onboarding/OnboardingViewModel.kt | 9 +++++++++ .../devices/v2/VectorSettingsDevicesFragment.kt | 2 +- 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 3a302feba0..1d8171a9e5 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -95,6 +95,16 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.qrCodeLoginEnabled, factory = VectorFeatures::isQrCodeLoginEnabled ), + createBooleanFeature( + label = "Allow QR Code Login for all servers", + key = DebugFeatureKeys.allowQrCodeLoginForAllServers, + factory = VectorFeatures::allowQrCodeLoginForAllServers + ), + createBooleanFeature( + label = "Show QR Code Login in Device Manager", + key = DebugFeatureKeys.allowReciprocateQrCodeLogin, + factory = VectorFeatures::allowReciprocateQrCodeLogin + ), createBooleanFeature( label = "Enable Voice Broadcast", key = DebugFeatureKeys.voiceBroadcastEnabled, diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index b5ffa28db7..701f2dcab8 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -82,6 +82,12 @@ class DebugVectorFeatures( override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled) ?: vectorFeatures.isQrCodeLoginEnabled() + override fun allowQrCodeLoginForAllServers() = read(DebugFeatureKeys.allowQrCodeLoginForAllServers) + ?: vectorFeatures.allowQrCodeLoginForAllServers() + + override fun allowReciprocateQrCodeLogin() = read(DebugFeatureKeys.allowReciprocateQrCodeLogin) + ?: vectorFeatures.allowReciprocateQrCodeLogin() + override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled) ?: vectorFeatures.isVoiceBroadcastEnabled() @@ -147,5 +153,7 @@ object DebugFeatureKeys { val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled") val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled") val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled") + val allowQrCodeLoginForAllServers = booleanPreferencesKey("allow-qr-code-login-for-all-servers") + val allowReciprocateQrCodeLogin = booleanPreferencesKey("allow-reciprocate-qr-code-login") val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 62eb0523b0..061276ba21 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -42,6 +42,8 @@ interface VectorFeatures { fun isNewAppLayoutFeatureEnabled(): Boolean fun isNewDeviceManagementEnabled(): Boolean fun isQrCodeLoginEnabled(): Boolean + fun allowQrCodeLoginForAllServers(): Boolean + fun allowReciprocateQrCodeLogin(): Boolean fun isVoiceBroadcastEnabled(): Boolean } @@ -60,5 +62,7 @@ class DefaultVectorFeatures : VectorFeatures { override fun isNewAppLayoutFeatureEnabled(): Boolean = true override fun isNewDeviceManagementEnabled(): Boolean = false override fun isQrCodeLoginEnabled(): Boolean = false + override fun allowQrCodeLoginForAllServers(): Boolean = false + override fun allowReciprocateQrCodeLogin(): Boolean = false override fun isVoiceBroadcastEnabled(): Boolean = false } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 03cf2f43e6..8e64948c6a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -124,7 +124,16 @@ class OnboardingViewModel @AssistedInject constructor( canLoginWithQrCode = false ) } + } else if (vectorFeatures.allowQrCodeLoginForAllServers()) { + // allow for all servers + setState { + copy( + canLoginWithQrCode = true + ) + } } else { + // check if selected server supports MSC3882 first + // FIXME: this should be checking the selected homeserver not defaultHomeserverUrl homeServerConnectionConfigFactory.create(defaultHomeserverUrl)?.let { val canLoginWithQrCode = authenticationService.isQrLoginSupported(it) setState { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 48f66cfc75..7e6da851dd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -158,7 +158,7 @@ class VectorSettingsDevicesFragment : } private fun initQrLoginView() { - if (!vectorFeatures.isQrCodeLoginEnabled()) { + if (!vectorFeatures.allowReciprocateQrCodeLogin()) { views.deviceListHeaderSignInWithQrCode.isVisible = false views.deviceListHeaderScanQrCodeButton.isVisible = false views.deviceListHeaderShowQrCodeButton.isVisible = false From de4232dff519b05063e0e671789802bbd96830cd Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 02:04:08 +0100 Subject: [PATCH 0055/1068] Fix logic for showing confirm button --- .../vector/app/features/login/qr/QrCodeLoginStatusFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 5451a16b44..05ed070915 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -115,7 +115,7 @@ class QrCodeLoginStatusFragment : VectorBaseFragment Date: Fri, 14 Oct 2022 11:25:41 +0200 Subject: [PATCH 0056/1068] Remove unused param --- .../devices/DeviceListBottomSheetViewModel.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 92687e1a37..e6f01ed144 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -42,7 +42,6 @@ data class DeviceListViewState( val userId: String, val allowDeviceAction: Boolean, val userItem: MatrixItem? = null, - val isMine: Boolean = false, val memberCrossSigningKey: MXCrossSigningInfo? = null, val cryptoDevices: Async> = Loading(), val selectedDevice: CryptoDeviceInfo? = null @@ -70,14 +69,12 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( userId = userId, allowDeviceAction = args.allowDeviceAction, userItem = it, - isMine = userId == session.myUserId ) } ?: return super.initialState(viewModelContext) } } init { - session.flow().liveUserCryptoDevices(initialState.userId) .execute { copy(cryptoDevices = it).also { From 0a6d620f27d3160d273f6d0a2b5e13936a333d23 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Oct 2022 11:38:24 +0200 Subject: [PATCH 0057/1068] getUser() can return null more often than before, since the SDK will retrieve data asynchronously. So ensure that the initial state can always be built. --- .../devices/DeviceListBottomSheetViewModel.kt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index e6f01ed144..eb23c5654e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -29,11 +29,13 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow @@ -60,17 +62,15 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { - override fun initialState(viewModelContext: ViewModelContext): DeviceListViewState? { + override fun initialState(viewModelContext: ViewModelContext): DeviceListViewState { val args = viewModelContext.args() val userId = args.userId val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession() - return session.getUser(userId)?.toMatrixItem()?.let { - DeviceListViewState( - userId = userId, - allowDeviceAction = args.allowDeviceAction, - userItem = it, - ) - } ?: return super.initialState(viewModelContext) + return DeviceListViewState( + userId = userId, + allowDeviceAction = args.allowDeviceAction, + userItem = session.getUserOrDefault(userId).toMatrixItem(), + ) } } @@ -86,6 +86,16 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor( .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } + + updateMatrixItem() + } + + private fun updateMatrixItem() { + viewModelScope.launch { + tryOrNull { session.userService().resolveUser(initialState.userId) } + ?.toMatrixItem() + ?.let { setState { copy(userItem = it) } } + } } override fun handle(action: DeviceListAction) { From 5a2d74443d7be8c969639153554af46d324aa73b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Oct 2022 12:07:52 +0200 Subject: [PATCH 0058/1068] Let GetProfileInfoTask store result into DB, except when we want to do bulk insertion. --- .../session/profile/GetProfileInfoTask.kt | 17 +++++++++++++++-- .../session/sync/handler/UpdateUserWorker.kt | 14 ++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index 40444edcab..4a20c68caf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -17,26 +17,39 @@ package org.matrix.android.sdk.internal.session.profile +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.user.UserEntityFactory import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal abstract class GetProfileInfoTask : Task { data class Params( - val userId: String + val userId: String, + val storeInDatabase: Boolean = true, ) } internal class DefaultGetProfileInfoTask @Inject constructor( private val profileAPI: ProfileAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val monarchy: Monarchy, ) : GetProfileInfoTask() { override suspend fun execute(params: Params): JsonDict { return executeRequest(globalErrorReceiver) { profileAPI.getProfile(params.userId) + }.also { user -> + if (params.storeInDatabase) { + // Insert into DB + monarchy.awaitTransaction { + it.insertOrUpdate(UserEntityFactory.create(User.fromJson(params.userId, user))) + } + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt index 1f840a82d5..1ee2fc4802 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt @@ -71,10 +71,16 @@ internal class UpdateUserWorker(context: Context, params: WorkerParameters, sess ?.saveLocally() } - private suspend fun fetchUsers(userIdsToFetch: Collection) = userIdsToFetch.mapNotNull { - tryOrNull { - val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) - User.fromJson(it, profileJson) + private suspend fun fetchUsers(userIdsToFetch: Collection): List { + return userIdsToFetch.mapNotNull { userId -> + tryOrNull { + val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params( + userId = userId, + // Bulk insert later, so tell the task not to store the User. + storeInDatabase = false, + )) + User.fromJson(userId, profileJson) + } } } From 032fa37b64486ad4ad2bd8846fbcd811f9e6b61c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Oct 2022 12:13:21 +0200 Subject: [PATCH 0059/1068] Create UserDataSource.getUserOrDefault(). --- .../matrix/android/sdk/internal/session/user/UserDataSource.kt | 2 ++ .../sdk/internal/session/widgets/helper/WidgetFactory.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt index f9feb04e97..98108008fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt @@ -66,6 +66,8 @@ internal class UserDataSource @Inject constructor( } } + fun getUserOrDefault(userId: String): User = getUser(userId) ?: User(userId) + fun getUserLive(userId: String): LiveData> { val liveData = monarchy.findAllMappedWithChanges( { UserEntity.where(it, userId) }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt index 8bd61a7bdf..8ede63c365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt @@ -74,7 +74,7 @@ internal class WidgetFactory @Inject constructor( // Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33 fun computeURL(widget: Widget, isLightTheme: Boolean): String? { var computedUrl = widget.widgetContent.url ?: return null - val myUser = userDataSource.getUser(userId) ?: User(userId) + val myUser = userDataSource.getUserOrDefault(userId) val keyValue = widget.widgetContent.data.mapKeys { "\$${it.key}" }.toMutableMap() From 7699628959c32b3743ca039f3e5c6f89abffc7f3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Oct 2022 12:25:55 +0200 Subject: [PATCH 0060/1068] Fix other potential issue when using Session.getUser() --- .../timeline/helper/LocationPinProvider.kt | 8 +++--- .../app/features/html/PillsPostProcessor.kt | 4 +-- .../location/LocationSharingViewModel.kt | 4 +-- .../map/UserLiveLocationViewStateMapper.kt | 26 +++++++++---------- .../notifications/NotifiableEventResolver.kt | 4 +-- .../NotificationDrawerManager.kt | 8 +++--- .../signout/soft/SoftLogoutViewModel.kt | 6 +++-- .../invite/SpaceInviteBottomSheetViewModel.kt | 6 ++--- .../usercode/UserCodeSharedViewModel.kt | 6 ++--- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt index 8ef910c931..7f276f2f73 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -29,7 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.glide.GlideApp import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject @@ -67,9 +67,9 @@ class LocationPinProvider @Inject constructor( activeSessionHolder .getActiveSession() - .getUser(userId) - ?.toMatrixItem() - ?.let { userItem -> + .getUserOrDefault(userId) + .toMatrixItem() + .let { userItem -> val size = dimensionConverter.dpToPx(44) val bgTintColor = matrixItemColorProvider.getColor(userItem) avatarRenderer.render(glideRequests, userItem, object : CustomTarget(size, size) { diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt index 5f20b7278e..85cfb76ff7 100644 --- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt @@ -27,7 +27,7 @@ import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.AvatarRenderer import io.noties.markwon.core.spans.LinkSpan import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -101,7 +101,7 @@ class PillsPostProcessor @AssistedInject constructor( private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? = if (roomId == null) { - sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem() + sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem() } else { sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, roomId)?.toMatrixItem() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 28e37a38eb..4c7abd99b8 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber @@ -101,7 +101,7 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun setUserItem() { - setState { copy(userItem = session.getUser(session.myUserId)?.toMatrixItem()) } + setState { copy(userItem = session.getUserOrDefault(session.myUserId).toMatrixItem()) } } private fun updatePin(isUserPin: Boolean? = true) { diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapper.kt b/vector/src/main/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapper.kt index 77f8c30fd3..f4f4a462ce 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapper.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapper.kt @@ -20,7 +20,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.toLocationData import kotlinx.coroutines.suspendCancellableCoroutine -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -45,19 +45,17 @@ class UserLiveLocationViewStateMapper @Inject constructor( else -> { locationPinProvider.create(userId) { pinDrawable -> val session = activeSessionHolder.getActiveSession() - session.getUser(userId)?.toMatrixItem()?.let { matrixItem -> - val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis() - val viewState = UserLiveLocationViewState( - matrixItem = matrixItem, - pinDrawable = pinDrawable, - locationData = locationData, - endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis, - locationTimestampMillis = locationTimestampMillis, - showStopSharingButton = userId == session.myUserId - ) - continuation.resume(viewState) { - // do nothing on cancellation - } + val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis() + val viewState = UserLiveLocationViewState( + matrixItem = session.getUserOrDefault(userId).toMatrixItem(), + pinDrawable = pinDrawable, + locationData = locationData, + endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis, + locationTimestampMillis = locationTimestampMillis, + showStopSharingButton = userId == session.myUserId + ) + continuation.resume(viewState) { + // do nothing on cancellation } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 4ee7da4b64..ba1d5c7f6f 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -38,7 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.supportsNotification import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent @@ -112,7 +112,7 @@ class NotifiableEventResolver @Inject constructor( val notificationAction = actions.toNotificationAction() return if (notificationAction.shouldNotify) { - val user = session.getUser(event.senderId!!) ?: return null + val user = session.getUserOrDefault(event.senderId!!) val timelineEvent = TimelineEvent( root = event, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 5f43ff6b90..2623045cf3 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,7 +27,7 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject @@ -186,11 +186,11 @@ class NotificationDrawerManager @Inject constructor( } private fun renderEvents(session: Session, eventsToRender: List>) { - val user = session.getUser(session.myUserId) + val user = session.getUserOrDefault(session.myUserId) // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash - val myUserDisplayName = user?.toMatrixItem()?.getBestName() ?: session.myUserId + val myUserDisplayName = user.toMatrixItem().getBestName() val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail( - contentUrl = user?.avatarUrl, + contentUrl = user.avatarUrl, width = avatarSize, height = avatarSize, method = ContentUrlResolver.ThumbnailMethod.SCALE diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index f3e2f82edc..117c298878 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -32,6 +32,7 @@ import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.hasUnsavedKeys import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.displayname.getBestName import im.vector.app.features.login.LoginMode import im.vector.app.features.login.toSsoState import kotlinx.coroutines.launch @@ -39,7 +40,8 @@ import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.LoginType import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault +import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber class SoftLogoutViewModel @AssistedInject constructor( @@ -68,7 +70,7 @@ class SoftLogoutViewModel @AssistedInject constructor( homeServerUrl = session.sessionParams.homeServerUrl, userId = userId, deviceId = session.sessionParams.deviceId.orEmpty(), - userDisplayName = session.getUser(userId)?.displayName ?: userId, + userDisplayName = session.getUserOrDefault(userId).toMatrixItem().getBestName(), hasUnsavedKeys = session.hasUnsavedKeys(), loginType = session.sessionParams.loginType, ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt index ea36908dd2..27f194e8d2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt @@ -34,7 +34,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -49,7 +49,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( session.getRoomSummary(initialState.spaceId)?.let { roomSummary -> val knownMembers = roomSummary.otherMemberIds.filter { session.roomService().getExistingDirectRoomWithUser(it) != null - }.mapNotNull { session.getUser(it) } + }.map { session.getUserOrDefault(it) } // put one with avatar first, and take 5 val peopleYouKnow = (knownMembers.filter { it.avatarUrl != null } + knownMembers.filter { it.avatarUrl == null }) .take(5) @@ -57,7 +57,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( setState { copy( summary = Success(roomSummary), - inviterUser = roomSummary.inviterId?.let { session.getUser(it) }?.let { Success(it) } ?: Uninitialized, + inviterUser = roomSummary.inviterId?.let { session.getUserOrDefault(it) }?.let { Success(it) } ?: Uninitialized, peopleYouKnow = Success(peopleYouKnow) ) } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 8c377cafd5..e76837f182 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.user.model.User @@ -46,10 +46,10 @@ class UserCodeSharedViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { - val user = session.getUser(initialState.userId) + val user = session.getUserOrDefault(initialState.userId) setState { copy( - matrixItem = user?.toMatrixItem(), + matrixItem = user.toMatrixItem(), shareLink = session.permalinkService().createPermalink(initialState.userId) ) } From 8bb5dcd553f866cbbe7bdcf4ceac7f79ed564238 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 14 Oct 2022 12:33:27 +0200 Subject: [PATCH 0061/1068] Changelog --- changelog.d/7372.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7372.bugfix diff --git a/changelog.d/7372.bugfix b/changelog.d/7372.bugfix new file mode 100644 index 0000000000..e63e00035b --- /dev/null +++ b/changelog.d/7372.bugfix @@ -0,0 +1 @@ +Handle properly when getUser returns null - prefer using getUserOrDefault From fd2814ea183a823ba5a7091f2723e96d3b695ba7 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Fri, 14 Oct 2022 11:49:24 +0100 Subject: [PATCH 0062/1068] Update issue automation for design Put only high priority issues in front of the design team, all of which the design team will aim to action to keep the queue at zero --- .github/workflows/triage-labelled.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 174e3c54c0..18ce26171c 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -48,7 +48,13 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - contains(github.event.issue.labels.*.name, 'X-Needs-Design') + contains(github.event.issue.labels.*.name, 'X-Needs-Design') && + (contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'O-Occasional')) || + contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'A11y')) steps: - uses: octokit/graphql-action@v2.x id: add_to_project From e439b72e48170c9bfcd0b57157c60816655d52e8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 13:46:57 +0100 Subject: [PATCH 0063/1068] Handle master key trust during E2EE set up --- .../android/sdk/api/rendezvous/Rendezvous.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index e33130e529..17f3a73181 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -176,10 +176,17 @@ class Rendezvous( crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) // TODO: what do we do with the master key? -// verificationResponse.master_key ?.let { -// // set master key as trusted -// crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, it) -// } + verificationResponse.master_key ?.let { masterKeyFromVerifyingDevice -> + // set master key as trusted + crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> + if (localMasterKey.unpaddedBase64PublicKey == masterKeyFromVerifyingDevice) { + Timber.tag(TAG).i("Setting master key as trusted") + crypto.crossSigningService().markMyMasterKeyAsTrusted() + } else { + Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") + } + } ?: Timber.tag(TAG).i("No local master key") + } ?: Timber.tag(TAG).i("No master key given by verifying device") // request secrets from the verifying device Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") From f999e7275952633a87d9cff123a4211546e7e735 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 14:01:13 +0100 Subject: [PATCH 0064/1068] Changelog --- changelog.d/7369.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7369.feature diff --git a/changelog.d/7369.feature b/changelog.d/7369.feature new file mode 100644 index 0000000000..240fac3516 --- /dev/null +++ b/changelog.d/7369.feature @@ -0,0 +1 @@ +Add logic for sign in with QR code From 411b766890ab33722b10be177bb1f3e64d963e75 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 14:17:19 +0100 Subject: [PATCH 0065/1068] Refactor to camelcase --- .../android/sdk/api/rendezvous/Rendezvous.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 17f3a73181..67bdeb2235 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -49,12 +49,12 @@ internal data class Payload( @Json val protocols: List? = null, @Json val protocol: String? = null, @Json val homeserver: String? = null, - @Json val login_token: String? = null, - @Json val device_id: String? = null, - @Json val device_key: String? = null, - @Json val verifying_device_id: String? = null, - @Json val verifying_device_key: String? = null, - @Json val master_key: String? = null + @Json(name = "login_token") val loginToken: String? = null, + @Json(name = "device_id") val deviceId: String? = null, + @Json(name = "device_key") val deviceKey: String? = null, + @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null, + @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null, + @Json(name = "master_key") val masterKey: String? = null ) private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value @@ -146,7 +146,7 @@ class Rendezvous( } val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") - val login_token = loginToken.login_token ?: throw RuntimeException("No login token returned") + val login_token = loginToken.loginToken ?: throw RuntimeException("No login token returned") Timber.tag(TAG).i("Got login_token: $login_token for $homeserver") @@ -159,14 +159,14 @@ class Rendezvous( val crypto = session.cryptoService() val deviceId = crypto.getMyDevice().deviceId val deviceKey = crypto.getMyDevice().fingerprint() - send(Payload(PayloadType.Progress, outcome = "success", device_id = deviceId, device_key = deviceKey)) + send(Payload(PayloadType.Progress, outcome = "success", deviceId = deviceId, deviceKey = deviceKey)) // await confirmation of verification val verificationResponse = receive() - val verifyingDeviceId = verificationResponse?.verifying_device_id ?: throw RuntimeException("No verifying device id returned") + val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned") val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) - if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifying_device_key) { + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") return } @@ -176,7 +176,7 @@ class Rendezvous( crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) // TODO: what do we do with the master key? - verificationResponse.master_key ?.let { masterKeyFromVerifyingDevice -> + verificationResponse.masterKey ?.let { masterKeyFromVerifyingDevice -> // set master key as trusted crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> if (localMasterKey.unpaddedBase64PublicKey == masterKeyFromVerifyingDevice) { From 6426ff40d351396741ebf8aa71da1b87d9661673 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 14:18:50 +0100 Subject: [PATCH 0066/1068] Linting --- .../org/matrix/android/sdk/api/rendezvous/Rendezvous.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 67bdeb2235..6403d17031 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -91,7 +91,11 @@ class Rendezvous( if (incompatible) { send(Payload(PayloadType.Finish, intent = ourIntent)) - val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) RendezvousFailureReason.OtherDeviceNotSignedIn else RendezvousFailureReason.OtherDeviceAlreadySignedIn + val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { + RendezvousFailureReason.OtherDeviceNotSignedIn + } else { + RendezvousFailureReason.OtherDeviceAlreadySignedIn + } channel.cancel(reason) } @@ -175,7 +179,6 @@ class Rendezvous( Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - // TODO: what do we do with the master key? verificationResponse.masterKey ?.let { masterKeyFromVerifyingDevice -> // set master key as trusted crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> From fdc55965ca0e3328d6ed4f32b00bde97f00941b9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 17:23:26 +0100 Subject: [PATCH 0067/1068] Linting --- .../matrix/android/sdk/api/rendezvous/RendezvousChannel.kt | 5 ++++- .../app/features/login/qr/QrCodeLoginStatusFragment.kt | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 588d034f10..9d2843ce66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -19,6 +19,9 @@ package org.matrix.android.sdk.api.rendezvous import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +/** + * Representation of a rendezvous channel such as that described by MSC3903 + */ interface RendezvousChannel { var transport: RendezvousTransport @@ -44,7 +47,7 @@ interface RendezvousChannel { */ suspend fun close() - // TODO: this should be transport independent in the future + // In future we probably want this to be a more generic RendezvousCode but it is suffice for now suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode suspend fun cancel(reason: RendezvousFailureReason) } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 556be3af4d..f0030d6763 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -80,7 +80,8 @@ class QrCodeLoginStatusFragment : VectorBaseFragment getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) + RendezvousFailureReason.UnsupportedAlgorithm, + RendezvousFailureReason.UnsupportedTransport -> getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) RendezvousFailureReason.Expired -> getString(R.string.qr_code_login_header_failed_timeout_description) RendezvousFailureReason.UserDeclined -> getString(R.string.qr_code_login_header_failed_denied_description) else -> getString(R.string.qr_code_login_header_failed_other_description) From bfe3daa37fda9cd42bc541b5c13c4f75bb58e221 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 17:25:06 +0100 Subject: [PATCH 0068/1068] Fix compile error from bad merge --- .../vector/app/features/login/qr/QrCodeLoginViewModel.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 7a51098439..af0df22ca3 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -24,8 +24,8 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.session.ConfigureAndStartSessionUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService @@ -37,7 +37,8 @@ class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, private val applicationContext: Context, private val authenticationService: AuthenticationService, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase ) : VectorViewModel(initialState) { val TAG: String = QrCodeLoginViewModel::class.java.simpleName @@ -97,7 +98,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( activeSessionHolder.setActiveSession(session) authenticationService.reset() - session.configureAndStart(applicationContext) + configureAndStartSessionUseCase.execute(session, startSyncing) rendezvous.completeVerificationOnNewDevice(session) From a3fc78594502953d86944d3f1ec0509c13921e7d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 14 Oct 2022 18:52:42 +0100 Subject: [PATCH 0069/1068] Fix missing param --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index af0df22ca3..81a00cf548 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -98,7 +98,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( activeSessionHolder.setActiveSession(session) authenticationService.reset() - configureAndStartSessionUseCase.execute(session, startSyncing) + configureAndStartSessionUseCase.execute(session) rendezvous.completeVerificationOnNewDevice(session) From 0cdc21649ec9d22220c61f37b82a693362ceb87f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Oct 2022 11:27:40 +0200 Subject: [PATCH 0070/1068] Fix unused import --- .../android/sdk/internal/session/widgets/helper/WidgetFactory.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt index 8ede63c365..a43c59a83b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.sender.SenderInfo -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType From b7570a3c5e781bc2f1d31aff07605ca6e7376bf4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Oct 2022 11:31:48 +0200 Subject: [PATCH 0071/1068] Fix compilation error --- .../android/sdk/internal/session/profile/GetProfileInfoTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index 4a20c68caf..22bb3d37b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -20,6 +20,7 @@ package org.matrix.android.sdk.internal.session.profile import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.user.UserEntityFactory @@ -37,7 +38,7 @@ internal abstract class GetProfileInfoTask : Task Date: Mon, 17 Oct 2022 10:52:24 +0100 Subject: [PATCH 0072/1068] Logging cleanup --- .../android/sdk/api/rendezvous/Rendezvous.kt | 22 ++++++++++++------- .../channels/ECDHRendezvousChannel.kt | 16 ++++---------- .../SimpleHttpRendezvousTransport.kt | 11 +++------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 6403d17031..b43b122cba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -36,9 +36,14 @@ import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber internal enum class PayloadType(val value: String) { - @Json(name = "m.login.start") Start("m.login.start"), - @Json(name = "m.login.finish") Finish("m.login.finish"), - @Json(name = "m.login.progress") Progress("m.login.progress") + @Json(name = "m.login.start") + Start("m.login.start"), + + @Json(name = "m.login.finish") + Finish("m.login.finish"), + + @Json(name = "m.login.progress") + Progress("m.login.progress") } @JsonClass(generateAdapter = true) @@ -150,12 +155,12 @@ class Rendezvous( } val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") - val login_token = loginToken.loginToken ?: throw RuntimeException("No login token returned") + val token = loginToken.loginToken ?: throw RuntimeException("No login token returned") - Timber.tag(TAG).i("Got login_token: $login_token for $homeserver") + Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver") val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver)) - return authenticationService.loginUsingQrLoginToken(hsConfig, login_token) + return authenticationService.loginUsingQrLoginToken(hsConfig, token) } suspend fun completeVerificationOnNewDevice(session: Session) { @@ -171,8 +176,8 @@ class Rendezvous( val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned") val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { - Timber.tag(TAG).w("Verifying device $verifyingDeviceId doesn't match: $verifyingDeviceFromServer") - return + Timber.tag(TAG).w("Verifying device $verifyingDeviceId key doesn't match: ${verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})") + throw RuntimeException("Key from verifying device doesn't match") } // set other device as verified @@ -187,6 +192,7 @@ class Rendezvous( crypto.crossSigningService().markMyMasterKeyAsTrusted() } else { Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") + throw RuntimeException("Master key from verifying device doesn't match") } } ?: Timber.tag(TAG).i("No local master key") } ?: Timber.tag(TAG).i("No master key given by verifying device") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 1c8bca5d1c..4d5ed30ac5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTran import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.internal.extensions.toUnsignedInt import org.matrix.olm.OlmSAS +import timber.log.Timber import java.security.SecureRandom import java.util.LinkedList import javax.crypto.Cipher @@ -93,7 +94,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu val isInitiator = theirPublicKey == null if (isInitiator) { -// Timber.tag(TAG).i("Waiting for other device to send their public key") + Timber.tag(TAG).i("Waiting for other device to send their public key") val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") if (res.key == null) { @@ -105,7 +106,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) } else { // send our public key unencrypted -// Timber.tag(TAG).i("Sending public key") + Timber.tag(TAG).i("Sending public key") send( ECDHPayload( algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, @@ -122,11 +123,6 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu aesKey = olmSAS!!.generateShortCode(aesInfo, 32) -// Timber.tag(TAG).i("Our public key: ${Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)}") -// Timber.tag(TAG).i("Their public key: ${Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)}") -// Timber.tag(TAG).i("AES info: $aesInfo") -// Timber.tag(TAG).i("AES key: ${Base64.encodeToString(aesKey, Base64.NO_WRAP)}") - val rawChecksum = olmSAS!!.generateShortCode(aesInfo, 5) return getDecimalCodeRepresentation(rawChecksum) } @@ -181,7 +177,6 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } private fun encrypt(plainText: ByteArray): ECDHPayload { -// Timber.tag(TAG).d("Encrypting: ${plainText.toString(Charsets.UTF_8)}") val iv = ByteArray(16) SecureRandom().nextBytes(iv) @@ -211,9 +206,6 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu plainText.addAll(encryptCipher.update(Base64.decode(payload.ciphertext, Base64.NO_WRAP)).toList()) plainText.addAll(encryptCipher.doFinal().toList()) - val plainTextBytes = plainText.toByteArray() - -// Timber.tag(TAG).d("Decrypted: ${plainTextBytes.toString(Charsets.UTF_8)}") - return plainTextBytes + return plainText.toByteArray() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 475a4fbe6c..004cf38e24 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -67,8 +67,6 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo // TODO: properly determine endpoint val uri = if (uri != null) uri!! else "https://rendezvous.lab.element.dev" -// Timber.tag(TAG).i("Sending data: ${data.toString(Charsets.UTF_8)} to $uri") - val httpClient = okhttp3.OkHttpClient.Builder().build() val request = Request.Builder() @@ -123,8 +121,6 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo val response = httpClient.newCall(request.build()).execute() try { -// Timber.tag(TAG).d("Received polling response: ${response.code} from $uri") - if (response.code == 404) { cancel(RendezvousFailureReason.Unknown) return null @@ -140,9 +136,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo response.header("etag")?.let { etag = it } - val data = response.body?.bytes() -// Timber.tag(TAG).d("Received data: ${data?.toString(Charsets.UTF_8)} from $uri with etag $etag") - return data + return response.body?.bytes() } done = false @@ -159,7 +153,8 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo var mappedReason = reason Timber.tag(TAG).i("$expiresAt") if (mappedReason == RendezvousFailureReason.Unknown && - expiresAt != null && Date() > expiresAt) { + expiresAt != null && Date() > expiresAt + ) { mappedReason = RendezvousFailureReason.Expired } From ed6bc01bef48b4266dbe34e600f8038b0dcc6a3a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 10:54:28 +0100 Subject: [PATCH 0073/1068] Resolve TODO --- .../api/rendezvous/transports/SimpleHttpRendezvousTransport.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 004cf38e24..dde5edcb93 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -64,8 +64,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo } val method = if (uri != null) "PUT" else "POST" - // TODO: properly determine endpoint - val uri = if (uri != null) uri!! else "https://rendezvous.lab.element.dev" + val uri = this.uri ?: throw RuntimeException("No rendezvous URI") val httpClient = okhttp3.OkHttpClient.Builder().build() From 33be5c257dcaa8c674486a77c5851d1a6a626f7e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 11:24:48 +0100 Subject: [PATCH 0074/1068] Refactor into dedicated files and companion objects --- .../android/sdk/api/rendezvous/Rendezvous.kt | 39 +++--------- .../channels/ECDHRendezvousChannel.kt | 59 ++++++++++--------- .../api/rendezvous/model/ECDHRendezvous.kt | 7 --- .../rendezvous/model/ECDHRendezvousCode.kt | 26 ++++++++ .../sdk/api/rendezvous/model/Payload.kt | 36 +++++++++++ .../sdk/api/rendezvous/model/PayloadType.kt | 30 ++++++++++ .../SimpleHttpRendezvousTransportDetails.kt | 25 ++++++++ .../SimpleHttpRendezvousTransport.kt | 13 ++-- 8 files changed, 159 insertions(+), 76 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index b43b122cba..4270d4a09c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -17,13 +17,13 @@ package org.matrix.android.sdk.api.rendezvous import android.net.Uri -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.Payload +import org.matrix.android.sdk.api.rendezvous.model.PayloadType import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import org.matrix.android.sdk.api.session.Session @@ -35,35 +35,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_S import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber -internal enum class PayloadType(val value: String) { - @Json(name = "m.login.start") - Start("m.login.start"), - - @Json(name = "m.login.finish") - Finish("m.login.finish"), - - @Json(name = "m.login.progress") - Progress("m.login.progress") -} - -@JsonClass(generateAdapter = true) -internal data class Payload( - @Json val type: PayloadType, - @Json val intent: RendezvousIntent? = null, - @Json val outcome: String? = null, - @Json val protocols: List? = null, - @Json val protocol: String? = null, - @Json val homeserver: String? = null, - @Json(name = "login_token") val loginToken: String? = null, - @Json(name = "device_id") val deviceId: String? = null, - @Json(name = "device_key") val deviceKey: String? = null, - @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null, - @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null, - @Json(name = "master_key") val masterKey: String? = null -) - -private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value - /** * Implementation of MSC3906 to sign in + E2EE set up using a QR code. */ @@ -72,6 +43,8 @@ class Rendezvous( val theirIntent: RendezvousIntent, ) { companion object { + private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value + fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { val parsed = MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) ?: throw RuntimeException("Invalid code") @@ -176,7 +149,9 @@ class Rendezvous( val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned") val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { - Timber.tag(TAG).w("Verifying device $verifyingDeviceId key doesn't match: ${verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})") + Timber.tag(TAG).w("Verifying device $verifyingDeviceId key doesn't match: ${ + verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})" + ) throw RuntimeException("Key from verifying device doesn't match") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 4d5ed30ac5..5dbac894ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm -import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.internal.extensions.toUnsignedInt import org.matrix.olm.OlmSAS @@ -40,39 +40,40 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -@JsonClass(generateAdapter = true) -data class ECDHPayload( - @Json val algorithm: SecureRendezvousChannelAlgorithm? = null, - @Json val key: String? = null, - @Json val ciphertext: String? = null, - @Json val iv: String? = null -) - -private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value - -fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - val b0 = byteArray[0].toUnsignedInt() // need unsigned byte - val b1 = byteArray[1].toUnsignedInt() // need unsigned byte - val b2 = byteArray[2].toUnsignedInt() // need unsigned byte - val b3 = byteArray[3].toUnsignedInt() // need unsigned byte - val b4 = byteArray[4].toUnsignedInt() // need unsigned byte - // (B0 << 5 | B1 >> 3) + 1000 - val first = (b0.shl(5) or b1.shr(3)) + 1000 - // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 - val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 - // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 - val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 - return "$first-$second-$third" -} - -const val ALGORITHM_SPEC = "AES/GCM/NoPadding" -const val KEY_SPEC = "AES" - /** * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903: * https://github.com/matrix-org/matrix-spec-proposals/pull/3903 */ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?) : RendezvousChannel { + companion object { + private const val ALGORITHM_SPEC = "AES/GCM/NoPadding" + private const val KEY_SPEC = "AES" + private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value + + private fun getDecimalCodeRepresentation(byteArray: ByteArray): String { + val b0 = byteArray[0].toUnsignedInt() // need unsigned byte + val b1 = byteArray[1].toUnsignedInt() // need unsigned byte + val b2 = byteArray[2].toUnsignedInt() // need unsigned byte + val b3 = byteArray[3].toUnsignedInt() // need unsigned byte + val b4 = byteArray[4].toUnsignedInt() // need unsigned byte + // (B0 << 5 | B1 >> 3) + 1000 + val first = (b0.shl(5) or b1.shr(3)) + 1000 + // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 + val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 + // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 + val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 + return "$first-$second-$third" + } + } + + @JsonClass(generateAdapter = true) + internal data class ECDHPayload( + @Json val algorithm: SecureRendezvousChannelAlgorithm? = null, + @Json val key: String? = null, + @Json val ciphertext: String? = null, + @Json val iv: String? = null + ) + private var olmSAS: OlmSAS? private val ourPublicKey: ByteArray private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt index b203101b66..0840e1ca2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransportDetails @JsonClass(generateAdapter = true) data class ECDHRendezvous( @@ -26,9 +25,3 @@ data class ECDHRendezvous( @Json val algorithm: SecureRendezvousChannelAlgorithm, @Json val key: String ) - -@JsonClass(generateAdapter = true) -data class ECDHRendezvousCode( - @Json val intent: RendezvousIntent, - @Json val rendezvous: ECDHRendezvous -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt new file mode 100644 index 0000000000..410c5c1036 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt @@ -0,0 +1,26 @@ +/* + * 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.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ECDHRendezvousCode( + @Json val intent: RendezvousIntent, + @Json val rendezvous: ECDHRendezvous +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt new file mode 100644 index 0000000000..5627452232 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt @@ -0,0 +1,36 @@ +/* + * 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.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class Payload( + @Json val type: PayloadType, + @Json val intent: RendezvousIntent? = null, + @Json val outcome: String? = null, + @Json val protocols: List? = null, + @Json val protocol: String? = null, + @Json val homeserver: String? = null, + @Json(name = "login_token") val loginToken: String? = null, + @Json(name = "device_id") val deviceId: String? = null, + @Json(name = "device_key") val deviceKey: String? = null, + @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null, + @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null, + @Json(name = "master_key") val masterKey: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt new file mode 100644 index 0000000000..9854e7c070 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.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.rendezvous.model + +import com.squareup.moshi.Json + +internal enum class PayloadType(val value: String) { + @Json(name = "m.login.start") + Start("m.login.start"), + + @Json(name = "m.login.finish") + Finish("m.login.finish"), + + @Json(name = "m.login.progress") + Progress("m.login.progress") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt new file mode 100644 index 0000000000..70a441d760 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt @@ -0,0 +1,25 @@ +/* + * 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.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SimpleHttpRendezvousTransportDetails( + @Json val uri: String +) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index dde5edcb93..03e8b0cda7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -26,22 +26,19 @@ import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.api.rendezvous.RendezvousTransport import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails -import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportType +import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails import timber.log.Timber import java.text.SimpleDateFormat import java.util.Date -private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value - -@JsonClass(generateAdapter = true) -data class SimpleHttpRendezvousTransportDetails( - @Json val uri: String -) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) - /** * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 */ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?, rendezvousUri: String?) : RendezvousTransport { + companion object { + private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value + } + override var ready = false private var cancelled = false private var uri: String? From 3be4a0ca2137c339b66b9b5aa12bb0617c15d935 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 11:25:09 +0100 Subject: [PATCH 0075/1068] Remove unused val --- .../java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 81a00cf548..8b70f61e80 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -35,7 +35,6 @@ import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, - private val applicationContext: Context, private val authenticationService: AuthenticationService, private val activeSessionHolder: ActiveSessionHolder, private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase From 48de8f4e345105668298ec238bbad5600c057ab9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 11:48:35 +0100 Subject: [PATCH 0076/1068] Fix bad merge --- .../session/homeserver/GetHomeServerCapabilitiesTask.kt | 2 ++ 1 file changed, 2 insertions(+) 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 d65e629b71..42bba5fe88 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 @@ -133,6 +133,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) } + homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ + getVersionResult?.doesServerSupportThreads().orFalse() } if (getMediaConfigResult != null) { From 506fa729ea8f9d77357cc0f1347d3227e6b51702 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 11:50:56 +0100 Subject: [PATCH 0077/1068] Cleanup --- .../android/sdk/api/rendezvous/Rendezvous.kt | 29 ++++++++++-------- .../channels/ECDHRendezvousChannel.kt | 1 + .../sdk/api/rendezvous/model/Outcome.kt | 30 +++++++++++++++++++ .../sdk/api/rendezvous/model/Payload.kt | 6 ++-- .../sdk/api/rendezvous/model/PayloadType.kt | 6 ++-- .../sdk/api/rendezvous/model/Protocol.kt | 24 +++++++++++++++ 6 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 4270d4a09c..e467ff06e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -22,8 +22,10 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.Outcome import org.matrix.android.sdk.api.rendezvous.model.Payload import org.matrix.android.sdk.api.rendezvous.model.PayloadType +import org.matrix.android.sdk.api.rendezvous.model.Protocol import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import org.matrix.android.sdk.api.session.Session @@ -68,7 +70,7 @@ class Rendezvous( Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible") if (incompatible) { - send(Payload(PayloadType.Finish, intent = ourIntent)) + send(Payload(PayloadType.FINISH, intent = ourIntent)) val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { RendezvousFailureReason.OtherDeviceNotSignedIn } else { @@ -93,14 +95,14 @@ class Rendezvous( Timber.tag(TAG).i("Waiting for protocols") val protocolsResponse = receive() - if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains("login_token")) { - send(Payload(PayloadType.Finish, outcome = "unsupported")) + if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) { + send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED)) Timber.tag(TAG).i("No supported protocol") cancel(RendezvousFailureReason.Unknown) return null } - send(Payload(PayloadType.Progress, protocol = "login_token")) + send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN)) return checksum } @@ -110,21 +112,23 @@ class Rendezvous( val loginToken = receive() - if (loginToken?.type == PayloadType.Finish) { + if (loginToken?.type == PayloadType.FINISH) { when (loginToken.outcome) { - "declined" -> { + Outcome.DECLINED -> { Timber.tag(TAG).i("Login declined by other device") channel.cancel(RendezvousFailureReason.UserDeclined) return null } - "unsupported" -> { + Outcome.UNSUPPORTED -> { Timber.tag(TAG).i("Not supported") channel.cancel(RendezvousFailureReason.HomeserverLacksSupport) return null } + else -> { + channel.cancel(RendezvousFailureReason.Unknown) + return null + } } - channel.cancel(RendezvousFailureReason.Unknown) - return null } val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") @@ -141,7 +145,7 @@ class Rendezvous( val crypto = session.cryptoService() val deviceId = crypto.getMyDevice().deviceId val deviceKey = crypto.getMyDevice().fingerprint() - send(Payload(PayloadType.Progress, outcome = "success", deviceId = deviceId, deviceKey = deviceKey)) + send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) // await confirmation of verification @@ -149,8 +153,9 @@ class Rendezvous( val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned") val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { - Timber.tag(TAG).w("Verifying device $verifyingDeviceId key doesn't match: ${ - verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})" + Timber.tag(TAG).w( + "Verifying device $verifyingDeviceId key doesn't match: ${ + verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})" ) throw RuntimeException("Key from verifying device doesn't match") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 5dbac894ae..489d20e588 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -50,6 +50,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu private const val KEY_SPEC = "AES" private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value + // n.b. we are only aver processing byte array that we have generated, so we can make assumptions about the length private fun getDecimalCodeRepresentation(byteArray: ByteArray): String { val b0 = byteArray[0].toUnsignedInt() // need unsigned byte val b1 = byteArray[1].toUnsignedInt() // need unsigned byte diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt new file mode 100644 index 0000000000..2dd6e7be28 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.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.rendezvous.model + +import com.squareup.moshi.Json + +enum class Outcome(val value: String) { + @Json(name = "success") + SUCCESS("success"), + + @Json(name = "declined") + DECLINED("declined"), + + @Json(name = "unsupported") + UNSUPPORTED("unsupported") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt index 5627452232..593177e625 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt @@ -23,9 +23,9 @@ import com.squareup.moshi.JsonClass internal data class Payload( @Json val type: PayloadType, @Json val intent: RendezvousIntent? = null, - @Json val outcome: String? = null, - @Json val protocols: List? = null, - @Json val protocol: String? = null, + @Json val outcome: Outcome? = null, + @Json val protocols: List? = null, + @Json val protocol: Protocol? = null, @Json val homeserver: String? = null, @Json(name = "login_token") val loginToken: String? = null, @Json(name = "device_id") val deviceId: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt index 9854e7c070..5ff4cd7cfa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -20,11 +20,11 @@ import com.squareup.moshi.Json internal enum class PayloadType(val value: String) { @Json(name = "m.login.start") - Start("m.login.start"), + START("m.login.start"), @Json(name = "m.login.finish") - Finish("m.login.finish"), + FINISH("m.login.finish"), @Json(name = "m.login.progress") - Progress("m.login.progress") + PROGRESS("m.login.progress") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt new file mode 100644 index 0000000000..18381984a5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt @@ -0,0 +1,24 @@ +/* + * 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.rendezvous.model + +import com.squareup.moshi.Json + +enum class Protocol(val value: String) { + @Json(name = "login_token") + LOGIN_TOKEN("login_token") +} From 4306c57236dccd50531bf20e8732031a84d6bb0e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 12:01:12 +0100 Subject: [PATCH 0078/1068] Thread safe use of OlmSAS --- .../channels/ECDHRendezvousChannel.kt | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 489d20e588..ca7083b297 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -90,43 +90,45 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } override suspend fun connect(): String { - if (olmSAS == null) { - throw RuntimeException("Channel closed") - } - val isInitiator = theirPublicKey == null + olmSAS ?.let { olmSAS -> + val isInitiator = theirPublicKey == null - if (isInitiator) { - Timber.tag(TAG).i("Waiting for other device to send their public key") - val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") + if (isInitiator) { + Timber.tag(TAG).i("Waiting for other device to send their public key") + val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") - if (res.key == null) { - throw RendezvousError( - "Unsupported algorithm: ${res.algorithm}", - RendezvousFailureReason.UnsupportedAlgorithm, + if (res.key == null) { + throw RendezvousError( + "Unsupported algorithm: ${res.algorithm}", + RendezvousFailureReason.UnsupportedAlgorithm, + ) + } + theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) + } else { + // send our public key unencrypted + Timber.tag(TAG).i("Sending public key") + send( + ECDHPayload( + algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, + key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) + ) ) } - theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) - } else { - // send our public key unencrypted - Timber.tag(TAG).i("Sending public key") - send( - ECDHPayload( - algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, - key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) - ) - ) - } - olmSAS!!.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + synchronized(olmSAS) { + olmSAS.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + olmSAS.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) - val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) - val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) - val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" + val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) + val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) + val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" - aesKey = olmSAS!!.generateShortCode(aesInfo, 32) + aesKey = olmSAS.generateShortCode(aesInfo, 32) - val rawChecksum = olmSAS!!.generateShortCode(aesInfo, 5) - return getDecimalCodeRepresentation(rawChecksum) + val rawChecksum = olmSAS.generateShortCode(aesInfo, 5) + return getDecimalCodeRepresentation(rawChecksum) + } + } ?: throw RuntimeException("Channel closed") } private suspend fun send(payload: ECDHPayload) { @@ -174,8 +176,13 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } override suspend fun close() { - olmSAS?.releaseSas() - olmSAS = null + olmSAS ?.let { + synchronized(it) { + // this does a double release check already so we don't re-check ourselves + it.releaseSas() + olmSAS = null + } + } } private fun encrypt(plainText: ByteArray): ECDHPayload { From fb86ab70a270e124b15b3e7af888381f32b78dd8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 12:05:32 +0100 Subject: [PATCH 0079/1068] Comments and error mapping --- .../transports/SimpleHttpRendezvousTransport.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 03e8b0cda7..e899a64e99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -99,9 +99,8 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo override suspend fun receive(): ByteArray? { val uri = uri ?: throw IllegalStateException("Rendezvous not set up") - var done = false val httpClient = okhttp3.OkHttpClient.Builder().build() - while (!done) { + while (true) { if (cancelled) { return null } @@ -117,8 +116,9 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo val response = httpClient.newCall(request.build()).execute() try { + // expired if (response.code == 404) { - cancel(RendezvousFailureReason.Unknown) + cancel(RendezvousFailureReason.Expired) return null } @@ -135,7 +135,8 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo return response.body?.bytes() } - done = false + // sleep for a second before polling again + // we rely on the server expiring the channel rather than checking it ourselves delay(1000) } finally { response.close() From 1976451c8164ee15b88186ae1fc6291513d158ec Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 12:23:14 +0100 Subject: [PATCH 0080/1068] Lint --- .../matrix/android/sdk/api/rendezvous/RendezvousChannel.kt | 2 +- .../rendezvous/transports/SimpleHttpRendezvousTransport.kt | 4 ---- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 9d2843ce66..31014e392d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent /** - * Representation of a rendezvous channel such as that described by MSC3903 + * Representation of a rendezvous channel such as that described by MSC3903. */ interface RendezvousChannel { var transport: RendezvousTransport diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index e899a64e99..3e9b11a68b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.api.rendezvous.transports -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import kotlinx.coroutines.delay import okhttp3.MediaType import okhttp3.Request @@ -142,8 +140,6 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo response.close() } } - - return null } override suspend fun cancel(reason: RendezvousFailureReason) { diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 8b70f61e80..36e88b284c 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.login.qr -import android.content.Context import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory From eb30ef166acff6fca015b321c71ce1b10a811c49 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 12:32:40 +0100 Subject: [PATCH 0081/1068] Improve 404 handling --- .../rendezvous/transports/SimpleHttpRendezvousTransport.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 3e9b11a68b..ca2a3425cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -75,7 +75,9 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo val response = httpClient.newCall(request.build()).execute() if (response.code == 404) { + // we set to unknown and the cancel method will rewrite the reason to expired if applicable cancel(RendezvousFailureReason.Unknown) + return } etag = response.header("etag") @@ -116,7 +118,8 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo try { // expired if (response.code == 404) { - cancel(RendezvousFailureReason.Expired) + // we set to unknown and the cancel method will rewrite the reason to expired if applicable + cancel(RendezvousFailureReason.Unknown) return null } From d616251f260515548f8b699514247b6e0a8ee9bc Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 13:41:27 +0100 Subject: [PATCH 0082/1068] Fix merge --- .../session/homeserver/GetHomeServerCapabilitiesTask.kt | 2 -- 1 file changed, 2 deletions(-) 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 9147c4e1cd..2c3cb440b6 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 @@ -132,8 +132,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) } - homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ - getVersionResult?.doesServerSupportThreads().orFalse() } if (getMediaConfigResult != null) { From 52147512185dfe6f73a81897919fd718548ddcc2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Oct 2022 15:00:39 +0200 Subject: [PATCH 0083/1068] Let the doctor be less strict and just warn. Keep the useful log "Is CI build: $isCiBuild". --- tools/gradle/doctor.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/gradle/doctor.gradle b/tools/gradle/doctor.gradle index c77d2eb338..705bb3e250 100644 --- a/tools/gradle/doctor.gradle +++ b/tools/gradle/doctor.gradle @@ -54,7 +54,7 @@ doctor { /** * Warn when not using parallel GC. Parallel GC is faster for build type tasks and is no longer the default in Java 9+. */ - warnWhenNotUsingParallelGC = !isCiBuild + warnWhenNotUsingParallelGC = true /** * Throws an error when the `Delete` or `clean` task has dependencies. * If a clean task depends on other tasks, clean can be reordered and made to run after the tasks that would produce @@ -82,7 +82,7 @@ doctor { /** * Fail on any `JAVA_HOME` issues. */ - failOnError.set(!isCiBuild) + failOnError.set(false) /** * Extra message text, if any, to show with the Gradle Doctor message. This is useful if you have a wiki page or * other instructions that you want to link for developers on your team if they encounter an issue. From b5eb15c7e3907030842f2389d48262e0b111a7e2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 15:54:30 +0200 Subject: [PATCH 0084/1068] Avoid using ActiveSessionHolder in a Fragment. Move the userId to `state.personalizationState` --- .../vector/app/features/onboarding/OnboardingViewModel.kt | 1 + .../vector/app/features/onboarding/OnboardingViewState.kt | 3 ++- .../ftueauth/FtueAuthChooseProfilePictureFragment.kt | 7 ++----- .../app/features/onboarding/OnboardingViewModelTest.kt | 5 +++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 9bb52fb1a5..93e2dfd154 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -641,6 +641,7 @@ class OnboardingViewModel @AssistedInject constructor( val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull() state.personalizationState.copy( + userId = session.myUserId, displayName = state.registrationState.selectedMatrixId?.let { MatrixPatterns.extractUserNameFromId(it) }, supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName, supportsChangingProfilePicture = capabilityOverrides?.canChangeAvatar ?: homeServerCapabilities.canChangeAvatar diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 99678ea5c1..b078f41ae2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -78,10 +78,11 @@ data class SelectedHomeserverState( @Parcelize data class PersonalizationState( + val userId: String = "", val supportsChangingDisplayName: Boolean = false, val supportsChangingProfilePicture: Boolean = false, val displayName: String? = null, - val selectedPictureUri: Uri? = null + val selectedPictureUri: Uri? = null, ) : Parcelable { fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt index 92d0aa2a0f..5450c74095 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt @@ -26,7 +26,6 @@ import androidx.core.view.isInvisible import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.databinding.FragmentFtueProfilePictureBinding @@ -42,7 +41,6 @@ class FtueAuthChooseProfilePictureFragment : AbstractFtueAuthFragment(), GalleryOrCameraDialogHelper.Listener { - @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory @Inject lateinit var avatarRenderer: AvatarRenderer @@ -85,10 +83,9 @@ class FtueAuthChooseProfilePictureFragment : views.profilePictureSubmit.isEnabled = hasSetPicture views.changeProfilePictureIcon.setImageResource(if (hasSetPicture) R.drawable.ic_edit else R.drawable.ic_camera_plain) - val session = activeSessionHolder.getActiveSession() val matrixItem = MatrixItem.UserItem( - id = session.myUserId, - displayName = state.personalizationState.displayName ?: "" + id = state.personalizationState.userId, + displayName = state.personalizationState.displayName.orEmpty() ) avatarRenderer.render(matrixItem, localUri = state.personalizationState.selectedPictureUri, imageView = views.profilePictureView) } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 82adc70fe3..865f1c79d6 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -687,7 +687,7 @@ class OnboardingViewModelTest { .assertStatesChanges( initialState, { copy(isLoading = true) }, - { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState(A_USERNAME)) } + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState(A_MATRIX_ID, A_USERNAME)) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() @@ -1196,7 +1196,8 @@ class OnboardingViewModelTest { } } -private fun HomeServerCapabilities.toPersonalisationState(displayName: String? = null) = PersonalizationState( +private fun HomeServerCapabilities.toPersonalisationState(userId: String, displayName: String? = null) = PersonalizationState( + userId = userId, supportsChangingDisplayName = canChangeDisplayName, supportsChangingProfilePicture = canChangeAvatar, displayName = displayName, From ae802dea315b594f9c02209b4bd681cf5a5635c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 15:56:55 +0200 Subject: [PATCH 0085/1068] Avoid using ActiveSessionHolder in a Fragment. Use the userId from `state.personalizationState` --- .../ftueauth/FtueAuthAccountCreatedFragment.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index a53ca52e85..2089dc5ad0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -26,21 +26,17 @@ import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.play -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.core.utils.styleMatchingText import im.vector.app.databinding.FragmentFtueAccountCreatedBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState -import javax.inject.Inject @AndroidEntryPoint class FtueAuthAccountCreatedFragment : AbstractFtueAuthFragment() { - @Inject lateinit var activeSessionHolder: ActiveSessionHolder - private var hasPlayedConfetti = false override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAccountCreatedBinding { @@ -53,15 +49,15 @@ class FtueAuthAccountCreatedFragment : } private fun setupViews() { - val userId = activeSessionHolder.getActiveSession().myUserId - val subtitle = getString(R.string.ftue_account_created_subtitle, userId).toSpannable().styleMatchingText(userId, Typeface.BOLD) - views.accountCreatedSubtitle.text = subtitle views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PersonalizeProfile) } views.accountCreatedTakeMeHome.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } views.accountCreatedTakeMeHomeCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } } override fun updateWithState(state: OnboardingViewState) { + val userId = state.personalizationState.userId + val subtitle = getString(R.string.ftue_account_created_subtitle, userId).toSpannable().styleMatchingText(userId, Typeface.BOLD) + views.accountCreatedSubtitle.text = subtitle val canPersonalize = state.personalizationState.supportsPersonalization() views.personalizeButtonGroup.isVisible = canPersonalize views.takeMeHomeButtonGroup.isVisible = !canPersonalize From f95d21ef1754ac1e471082244319c64c9b054b27 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 16:32:50 +0200 Subject: [PATCH 0086/1068] Inject member in VectorBaseActivity instead of using SingletonEntryPoint --- .../app/core/platform/VectorBaseActivity.kt | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 7e61958565..413249e79c 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -105,7 +105,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected var analyticsScreenName: MobileScreen.ScreenName? = null - protected lateinit var analyticsTracker: AnalyticsTracker + @Inject lateinit var analyticsTracker: AnalyticsTracker /* ========================================================================================== * View @@ -149,26 +149,22 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver * ========================================================================================== */ private lateinit var configurationViewModel: ConfigurationViewModel - private lateinit var sessionListener: SessionListener - protected lateinit var bugReporter: BugReporter - private lateinit var pinLocker: PinLocker + @Inject lateinit var sessionListener: SessionListener + @Inject lateinit var bugReporter: BugReporter + @Inject lateinit var pinLocker: PinLocker @Inject lateinit var rageShake: RageShake @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var fontScalePreferences: FontScalePreferences @Inject lateinit var vectorLocale: VectorLocaleProvider + @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var navigator: Navigator + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var vectorPreferences: VectorPreferences // For debug only @Inject lateinit var debugReceiver: DebugReceiver - @Inject - lateinit var vectorFeatures: VectorFeatures - - lateinit var navigator: Navigator - private set - - private lateinit var activeSessionHolder: ActiveSessionHolder - private lateinit var vectorPreferences: VectorPreferences // Filter for multiple invalid token error private var mainActivityStarted = false @@ -205,7 +201,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @CallSuper override fun onCreate(savedInstanceState: Bundle?) { Timber.i("onCreate Activity ${javaClass.simpleName}") - val singletonEntryPoint = singletonEntryPoint() val activityEntryPoint = EntryPointAccessors.fromActivity(this, ActivityEntryPoint::class.java) ThemeUtils.setActivityTheme(this, getOtherThemes()) viewModelFactory = activityEntryPoint.viewModelFactory() @@ -213,12 +208,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener) setupMenu() configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java) - bugReporter = singletonEntryPoint.bugReporter() - pinLocker = singletonEntryPoint.pinLocker() - analyticsTracker = singletonEntryPoint.analyticsTracker() - navigator = singletonEntryPoint.navigator() - activeSessionHolder = singletonEntryPoint.activeSessionHolder() - vectorPreferences = singletonEntryPoint.vectorPreferences() configurationViewModel.activityRestarter.observe(this) { if (!it.hasBeenHandled) { // Recreate the Activity because configuration has changed @@ -230,7 +219,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver navigator.openPinCode(this, pinStartForActivityResult, PinMode.AUTH) } } - sessionListener = singletonEntryPoint.sessionListener() sessionListener.globalErrorLiveData.observeEvent(this) { handleGlobalError(it) } From cbd0972eca266dbe8decd38221af196fffc7adcb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 16:16:37 +0200 Subject: [PATCH 0087/1068] Reuse injected members in parent activity. --- .../vector/app/features/debug/DebugMenuActivity.kt | 4 ---- .../java/im/vector/app/features/MainActivity.kt | 13 +++++-------- .../keysbackup/restore/KeysBackupRestoreActivity.kt | 4 ---- .../keysbackup/setup/KeysBackupSetupActivity.kt | 2 -- .../im/vector/app/features/home/HomeActivity.kt | 4 ---- .../vector/app/features/link/LinkHandlerActivity.kt | 10 ++++------ .../app/features/share/IncomingShareActivity.kt | 4 ---- .../app/features/webview/VectorWebViewActivity.kt | 3 --- .../vector/app/features/widgets/WidgetActivity.kt | 4 ---- 9 files changed, 9 insertions(+), 39 deletions(-) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index 005e9c499b..e74caac299 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -26,7 +26,6 @@ import androidx.core.app.Person import androidx.core.content.getSystemService import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.time.Clock @@ -59,9 +58,6 @@ class DebugMenuActivity : VectorBaseActivity() { override fun getBinding() = ActivityDebugMenuBinding.inflate(layoutInflater) - @Inject - lateinit var activeSessionHolder: ActiveSessionHolder - @Inject lateinit var clock: Clock diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 0040962b73..1040727722 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -131,13 +131,10 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity private lateinit var args: MainActivityArgs @Inject lateinit var notificationDrawerManager: NotificationDrawerManager - @Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var errorFormatter: ErrorFormatter - @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var uiStateRepository: UiStateRepository @Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var pinCodeHelper: PinCodeHelper - @Inject lateinit var pinLocker: PinLocker @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var vectorAnalytics: VectorAnalytics @Inject lateinit var lockScreenKeyRepository: LockScreenKeyRepository @@ -232,7 +229,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } private fun doCleanUp() { - val session = sessionHolder.getSafeActiveSession() + val session = activeSessionHolder.getSafeActiveSession() if (session == null) { startNextActivityAndFinish() return @@ -244,7 +241,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity lifecycleScope.launch { // Just do the local cleanup Timber.w("Account deactivated, start app") - sessionHolder.clearActiveSession() + activeSessionHolder.clearActiveSession() doLocalCleanup(clearPreferences = true, onboardingStore) startNextActivityAndFinish() } @@ -258,7 +255,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity return@launch } Timber.w("SIGN_OUT: success, start app") - sessionHolder.clearActiveSession() + activeSessionHolder.clearActiveSession() doLocalCleanup(clearPreferences = true, onboardingStore) startNextActivityAndFinish() } @@ -330,10 +327,10 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity args.isUserLoggedOut -> // the homeserver has invalidated the token (password changed, device deleted, other security reasons) SignedOutActivity.newIntent(this) - sessionHolder.hasActiveSession() -> + activeSessionHolder.hasActiveSession() -> // We have a session. // Check it can be opened - if (sessionHolder.getActiveSession().isOpenable) { + if (activeSessionHolder.getActiveSession().isOpenable) { HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true) } else { // The token is still invalid diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt index f7964cf0ed..4adccf3953 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt @@ -22,7 +22,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.registerStartForActivityResult @@ -32,7 +31,6 @@ import im.vector.app.features.crypto.quads.SharedSecureStorageActivity import im.vector.app.features.workers.signout.ServerBackupStatusAction import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME -import javax.inject.Inject @AndroidEntryPoint class KeysBackupRestoreActivity : SimpleFragmentActivity() { @@ -56,8 +54,6 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() { super.onBackPressed() } - @Inject lateinit var activeSessionHolder: ActiveSessionHolder - override fun initUiAndData() { super.initUiAndData() viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt index 8238da8245..4473d54765 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dialogs.ExportKeysDialog import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.queryExportKeys @@ -45,7 +44,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { private lateinit var viewModel: KeysBackupSetupSharedViewModel @Inject lateinit var keysExporter: KeysExporter - @Inject lateinit var activeSessionHolder: ActiveSessionHolder private val session by lazy { activeSessionHolder.getActiveSession() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 552b78bbb8..2df94fecad 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -37,7 +37,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.SpaceStateHandler -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment @@ -77,7 +76,6 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.rageshake.ReportType import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler -import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.spaces.SpaceCreationActivity import im.vector.app.features.spaces.SpacePreviewActivity @@ -129,11 +127,9 @@ class HomeActivity : private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel() - @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var pushersManager: PushersManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager - @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var permalinkHandler: PermalinkHandler diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 0bdec53f60..2526dc6ed7 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -24,7 +24,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity @@ -46,7 +45,6 @@ import javax.inject.Inject @AndroidEntryPoint class LinkHandlerActivity : VectorBaseActivity() { - @Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var permalinkHandler: PermalinkHandler @@ -103,7 +101,7 @@ class LinkHandlerActivity : VectorBaseActivity() { } private fun handleConfigUrl(uri: Uri) { - if (sessionHolder.hasActiveSession()) { + if (activeSessionHolder.hasActiveSession()) { displayAlreadyLoginPopup(uri) } else { // user is not yet logged in, this is the nominal case @@ -114,7 +112,7 @@ class LinkHandlerActivity : VectorBaseActivity() { private fun handleSupportedHostUrl() { // If we are not logged in, open login screen. // In the future, we might want to relaunch the process after login. - if (!sessionHolder.hasActiveSession()) { + if (!activeSessionHolder.hasActiveSession()) { startLoginActivity() return } @@ -152,7 +150,7 @@ class LinkHandlerActivity : VectorBaseActivity() { } private fun safeSignout(uri: Uri) { - val session = sessionHolder.getSafeActiveSession() + val session = activeSessionHolder.getSafeActiveSession() if (session == null) { // Should not happen startLoginActivity(uri) @@ -161,7 +159,7 @@ class LinkHandlerActivity : VectorBaseActivity() { try { session.signOutService().signOut(true) Timber.d("## displayAlreadyLoginPopup(): logout succeeded") - sessionHolder.clearActiveSession() + activeSessionHolder.clearActiveSession() startLoginActivity(uri) } catch (failure: Throwable) { displayError(failure) diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt index 3d603e3f6a..a78e6c95c0 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt @@ -20,22 +20,18 @@ import android.content.Intent import android.os.Bundle import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.MainActivity import im.vector.app.features.start.StartAppViewModel -import javax.inject.Inject @AndroidEntryPoint class IncomingShareActivity : VectorBaseActivity() { private val startAppViewModel: StartAppViewModel by viewModel() - @Inject lateinit var activeSessionHolder: ActiveSessionHolder - private val launcher = registerStartForActivityResult { if (it.resultCode == RESULT_OK) { handleAppStarted() diff --git a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt index c97f87972e..6f60ed9958 100644 --- a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt @@ -21,12 +21,10 @@ import android.content.Intent import android.webkit.WebChromeClient import android.webkit.WebView import dagger.hilt.android.AndroidEntryPoint -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityVectorWebViewBinding import im.vector.lib.core.utils.compat.getSerializableCompat import org.matrix.android.sdk.api.session.Session -import javax.inject.Inject /** * This class is responsible for managing a WebView @@ -39,7 +37,6 @@ class VectorWebViewActivity : VectorBaseActivity() override fun getBinding() = ActivityVectorWebViewBinding.inflate(layoutInflater) - @Inject lateinit var activeSessionHolder: ActiveSessionHolder val session: Session by lazy { activeSessionHolder.getActiveSession() } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 586e4dcc6e..eeb3959ef8 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -39,7 +39,6 @@ import im.vector.app.R import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityWidgetBinding -import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewEvents import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewModel @@ -48,7 +47,6 @@ import im.vector.lib.core.utils.compat.getSerializableCompat import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Content import java.io.Serializable -import javax.inject.Inject @AndroidEntryPoint class WidgetActivity : VectorBaseActivity() { @@ -83,8 +81,6 @@ class WidgetActivity : VectorBaseActivity() { private val viewModel: WidgetViewModel by viewModel() private val permissionViewModel: RoomWidgetPermissionViewModel by viewModel() - @Inject lateinit var vectorPreferences: VectorPreferences - override fun getBinding() = ActivityWidgetBinding.inflate(layoutInflater) override fun getTitleRes() = R.string.room_widget_activity_title From b3068c017da17d07f040c8060f9e3d8b369917db Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 16:20:20 +0200 Subject: [PATCH 0088/1068] Inject ErrorFormatter in the parent Activity (often used) --- .../java/im/vector/app/core/platform/VectorBaseActivity.kt | 3 ++- vector/src/main/java/im/vector/app/features/MainActivity.kt | 5 ----- .../im/vector/app/features/call/dialpad/PstnDialActivity.kt | 2 -- .../app/features/call/transfer/CallTransferActivity.kt | 4 ---- .../app/features/createdirect/CreateDirectRoomActivity.kt | 3 --- .../features/crypto/quads/SharedSecureStorageActivity.kt | 3 --- .../vector/app/features/invite/InviteUsersToRoomActivity.kt | 3 --- .../java/im/vector/app/features/link/LinkHandlerActivity.kt | 2 -- .../roomprofile/settings/joinrule/RoomJoinRuleActivity.kt | 5 ----- .../vector/app/features/signout/soft/SoftLogoutActivity.kt | 2 -- .../app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt | 6 +----- .../im/vector/app/features/terms/ReviewTermsActivity.kt | 4 ---- 12 files changed, 3 insertions(+), 39 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 413249e79c..4e5116eda9 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -56,6 +56,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.dialogs.DialogLocker import im.vector.app.core.dialogs.UnrecognizedCertificateDialog +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.fatalError import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeNotNull @@ -161,11 +162,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var navigator: Navigator @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var errorFormatter: ErrorFormatter // For debug only @Inject lateinit var debugReceiver: DebugReceiver - // Filter for multiple invalid token error private var mainActivityStarted = false diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 1040727722..c197cfccf3 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -30,8 +30,6 @@ import com.bumptech.glide.Glide import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.vectorStore import im.vector.app.core.platform.VectorBaseActivity @@ -42,13 +40,11 @@ import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.ShortcutsHandler import im.vector.app.features.notifications.NotificationDrawerManager -import im.vector.app.features.pin.PinLocker import im.vector.app.features.pin.UnlockedActivity import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.session.VectorSessionStore -import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.start.StartAppAction import im.vector.app.features.start.StartAppAndroidService @@ -131,7 +127,6 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity private lateinit var args: MainActivityArgs @Inject lateinit var notificationDrawerManager: NotificationDrawerManager - @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var uiStateRepository: UiStateRepository @Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var pinCodeHelper: PinCodeHelper diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt index 251195ddc4..1a7571598e 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -39,7 +38,6 @@ class PstnDialActivity : SimpleFragmentActivity() { @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var directRoomHelper: DirectRoomHelper @Inject lateinit var session: Session - @Inject lateinit var errorFormatter: ErrorFormatter private var progress: AppCompatDialog? = null diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt index 25bfb6c0e9..ae168c5f92 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -25,12 +25,10 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityCallTransferBinding import im.vector.lib.core.utils.compat.getParcelableCompat import kotlinx.parcelize.Parcelize -import javax.inject.Inject @Parcelize data class CallTransferArgs(val callId: String) : Parcelable @@ -40,8 +38,6 @@ private const val USER_LIST_FRAGMENT_TAG = "USER_LIST_FRAGMENT_TAG" @AndroidEntryPoint class CallTransferActivity : VectorBaseActivity() { - @Inject lateinit var errorFormatter: ErrorFormatter - private lateinit var sectionsPagerAdapter: CallTransferPagerAdapter private val callTransferViewModel: CallTransferViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index fd28235a51..acaf24dca7 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -32,7 +32,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.platform.SimpleFragmentActivity @@ -58,7 +57,6 @@ import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import java.net.HttpURLConnection -import javax.inject.Inject @AndroidEntryPoint class CreateDirectRoomActivity : SimpleFragmentActivity() { @@ -67,7 +65,6 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private val qrViewModel: QrCodeScannerViewModel by viewModel() private lateinit var sharedActionViewModel: UserListSharedActionViewModel - @Inject lateinit var errorFormatter: ErrorFormatter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index b39992256d..d393636a8e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -30,13 +30,11 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.crypto.recover.SetupMode import kotlinx.parcelize.Parcelize -import javax.inject.Inject import kotlin.reflect.KClass @AndroidEntryPoint @@ -54,7 +52,6 @@ class SharedSecureStorageActivity : ) : Parcelable private val viewModel: SharedSecureStorageViewModel by viewModel() - @Inject lateinit var errorFormatter: ErrorFormatter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 86f061849b..7f514d2ad2 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -27,7 +27,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.platform.SimpleFragmentActivity @@ -47,7 +46,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.Failure import java.net.HttpURLConnection -import javax.inject.Inject @Parcelize data class InviteUsersToRoomArgs(val roomId: String) : Parcelable @@ -57,7 +55,6 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { private val viewModel: InviteUsersToRoomViewModel by viewModel() private lateinit var sharedActionViewModel: UserListSharedActionViewModel - @Inject lateinit var errorFormatter: ErrorFormatter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 2526dc6ed7..1ca67e1fb7 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -24,7 +24,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast @@ -45,7 +44,6 @@ import javax.inject.Inject @AndroidEntryPoint class LinkHandlerActivity : VectorBaseActivity() { - @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var permalinkHandler: PermalinkHandler private val startAppViewModel: StartAppViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index 205fb86377..818300ac72 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -29,7 +29,6 @@ import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.toMvRxBundle @@ -44,7 +43,6 @@ import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRul import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.lib.core.utils.compat.getParcelableCompat -import javax.inject.Inject @AndroidEntryPoint class RoomJoinRuleActivity : VectorBaseActivity() { @@ -53,9 +51,6 @@ class RoomJoinRuleActivity : VectorBaseActivity() { private lateinit var roomProfileArgs: RoomProfileArgs - @Inject - lateinit var errorFormatter: ErrorFormatter - val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel() override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt index 729bd2541e..15a86c869b 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt @@ -25,7 +25,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.replaceFragment import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -45,7 +44,6 @@ class SoftLogoutActivity : LoginActivity() { private val softLogoutViewModel: SoftLogoutViewModel by viewModel() @Inject lateinit var session: Session - @Inject lateinit var errorFormatter: ErrorFormatter override fun initUiAndData() { super.initUiAndData() diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt index 80959c61a1..6f9aef401d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt @@ -29,7 +29,6 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.extensions.setTextOrHide @@ -37,16 +36,13 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleLoadingBinding import im.vector.app.features.spaces.SpaceBottomSheetSettingsArgs import im.vector.lib.core.utils.compat.getParcelableExtraCompat -import javax.inject.Inject @AndroidEntryPoint class SpaceLeaveAdvancedActivity : VectorBaseActivity() { override fun getBinding(): ActivitySimpleLoadingBinding = ActivitySimpleLoadingBinding.inflate(layoutInflater) - val leaveViewModel: SpaceLeaveAdvancedViewModel by viewModel() - - @Inject lateinit var errorFormatter: ErrorFormatter + private val leaveViewModel: SpaceLeaveAdvancedViewModel by viewModel() override fun showWaitingView(text: String?) { hideKeyboard() diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt index d261a8d160..6e34113012 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt @@ -22,18 +22,14 @@ import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.lib.core.utils.compat.getParcelableExtraCompat import org.matrix.android.sdk.api.session.terms.TermsService -import javax.inject.Inject @AndroidEntryPoint class ReviewTermsActivity : SimpleFragmentActivity() { - @Inject lateinit var errorFormatter: ErrorFormatter - private val reviewTermsViewModel: ReviewTermsViewModel by viewModel() override fun initUiAndData() { From aa806ed2c61a7fd8cf4dcac36921cb61615d6730 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 16:25:48 +0200 Subject: [PATCH 0089/1068] More cleanup on @Inject members --- .../vector/app/features/debug/DebugMenuActivity.kt | 3 +-- .../features/home/room/threads/ThreadsActivity.kt | 3 +-- .../media/VectorAttachmentViewerActivity.kt | 13 ++++--------- .../app/features/roomprofile/RoomProfileActivity.kt | 3 +-- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index e74caac299..f431192efd 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -58,8 +58,7 @@ class DebugMenuActivity : VectorBaseActivity() { override fun getBinding() = ActivityDebugMenuBinding.inflate(layoutInflater) - @Inject - lateinit var clock: Clock + @Inject lateinit var clock: Clock private lateinit var buffer: ByteArray 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 0ee0beb2e6..b3f2ef1f75 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 @@ -40,8 +40,7 @@ import javax.inject.Inject @AndroidEntryPoint class ThreadsActivity : VectorBaseActivity() { - @Inject - lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var avatarRenderer: AvatarRenderer // private val roomThreadDetailFragment: RoomThreadDetailFragment? // get() { diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index d523d99d76..089fdcebd4 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -69,14 +69,9 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt val sharedTransitionName: String? ) : Parcelable - @Inject - lateinit var sessionHolder: ActiveSessionHolder - - @Inject - lateinit var dataSourceFactory: AttachmentProviderFactory - - @Inject - lateinit var imageContentRenderer: ImageContentRenderer + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var dataSourceFactory: AttachmentProviderFactory + @Inject lateinit var imageContentRenderer: ImageContentRenderer private val viewModel: VectorAttachmentViewerViewModel by viewModel() private val errorFormatter by lazy(LazyThreadSafetyMode.NONE) { singletonEntryPoint().errorFormatter() } @@ -128,7 +123,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } } - val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() } + val session = activeSessionHolder.getSafeActiveSession() ?: return Unit.also { finish() } val room = args.roomId?.let { session.getRoom(it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 2bb3e26384..526d676dee 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -69,8 +69,7 @@ class RoomProfileActivity : private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel() - @Inject - lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore + @Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore override fun getBinding(): ActivitySimpleBinding { return ActivitySimpleBinding.inflate(layoutInflater) From cfca776d01954dc248f547b41bbd6b0400beb34c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 23 Sep 2022 16:44:39 +0200 Subject: [PATCH 0090/1068] Create PermalinkUseCase to avoid injecting the Session in the View. --- .../app/features/home/HomeDrawerFragment.kt | 4 +- .../features/home/NewHomeDetailFragment.kt | 2 - .../home/room/detail/TimelineFragment.kt | 8 ++-- .../features/permalink/PermalinkUseCase.kt | 39 +++++++++++++++++++ .../roomdirectory/PublicRoomsFragment.kt | 6 +-- 5 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 106fbc7281..b75d5c2e99 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -32,6 +32,7 @@ import im.vector.app.core.resources.BuildMeta import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.databinding.FragmentHomeDrawerBinding import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.permalink.PermalinkUseCase import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.spaces.SpaceListFragment @@ -49,6 +50,7 @@ class HomeDrawerFragment : @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var permalinkUseCase: PermalinkUseCase private lateinit var sharedActionViewModel: HomeSharedActionViewModel @@ -101,7 +103,7 @@ class HomeDrawerFragment : } views.homeDrawerInviteFriendButton.debouncedClicks { - session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink -> + permalinkUseCase.createPermalinkOfCurrentUser()?.let { permalink -> analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends)) val text = getString(R.string.invite_friends_text, permalink) diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index 66bb9ef876..5956646eab 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -61,7 +61,6 @@ import im.vector.app.features.workers.signout.ServerBackupStatusAction import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -80,7 +79,6 @@ class NewHomeDetailFragment : @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var spaceStateHandler: SpaceStateHandler - @Inject lateinit var session: Session @Inject lateinit var buildMeta: BuildMeta private val viewModel: HomeDetailViewModel by fragmentViewModel() 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 ad8b2f28f3..b17cefa6b7 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 @@ -169,6 +169,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler +import im.vector.app.features.permalink.PermalinkUseCase import im.vector.app.features.poll.PollMode import im.vector.app.features.reactions.EmojiReactionPickerActivity import im.vector.app.features.roomprofile.RoomProfileActivity @@ -247,6 +248,7 @@ class TimelineFragment : @Inject lateinit var clock: Clock @Inject lateinit var vectorFeatures: VectorFeatures @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + @Inject lateinit var permalinkUseCase: PermalinkUseCase companion object { const val MAX_TYPING_MESSAGE_USERS_COUNT = 4 @@ -867,7 +869,7 @@ class TimelineFragment : } R.id.menu_thread_timeline_copy_link -> { getRootThreadEventId()?.let { - val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it) + val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, it) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } @@ -879,7 +881,7 @@ class TimelineFragment : } R.id.menu_thread_timeline_share -> { getRootThreadEventId()?.let { - val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it) + val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, it) shareText(requireContext(), permalink) } true @@ -1788,7 +1790,7 @@ class TimelineFragment : } } is EventSharedAction.CopyPermalink -> { - val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, action.eventId) + val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt new file mode 100644 index 0000000000..d9c629d5d0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt @@ -0,0 +1,39 @@ +/* + * Copyright 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.permalink + +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +/** + * Contains synchronous methods to create permalinks from the Session. + */ +class PermalinkUseCase @Inject constructor( + private val session: Session, +) { + fun createPermalinkOfCurrentUser(): String? { + return createPermalink(session.myUserId) + } + + fun createPermalink(id: String): String? { + return session.permalinkService().createPermalink(id) + } + + fun createPermalink(roomId: String, eventId: String): String { + return session.permalinkService().createPermalink(roomId, eventId) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index 7b5cc20910..89ee7434df 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -38,11 +38,11 @@ import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler +import im.vector.app.features.permalink.PermalinkUseCase import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import reactivecircus.flowbinding.appcompat.queryTextChanges import timber.log.Timber @@ -60,7 +60,7 @@ class PublicRoomsFragment : @Inject lateinit var publicRoomsController: PublicRoomsController @Inject lateinit var permalinkHandler: PermalinkHandler - @Inject lateinit var session: Session + @Inject lateinit var permalinkUseCase: PermalinkUseCase private val viewModel: RoomDirectoryViewModel by activityViewModel() private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel @@ -128,7 +128,7 @@ class PublicRoomsFragment : override fun onUnknownRoomClicked(roomIdOrAlias: String) { viewLifecycleOwner.lifecycleScope.launch { - val permalink = session.permalinkService().createPermalink(roomIdOrAlias) + val permalink = permalinkUseCase.createPermalink(roomIdOrAlias) val isHandled = permalinkHandler .launch(requireActivity(), permalink, object : NavigationInterceptor { override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { From 37f34dbdfd63434c9912e3dcc8f4ba2ea9de9391 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Sep 2022 16:41:19 +0200 Subject: [PATCH 0091/1068] Expect the userId from the fakeSession --- .../vector/app/features/onboarding/OnboardingViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 865f1c79d6..718f1ec7a9 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -687,7 +687,7 @@ class OnboardingViewModelTest { .assertStatesChanges( initialState, { copy(isLoading = true) }, - { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState(A_MATRIX_ID, A_USERNAME)) } + { copy(isLoading = false, personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState("@fake:server.fake", A_USERNAME)) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() From 3bc3da1073b1c592755fa8af9365c470ca2ead33 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 4 Oct 2022 16:49:03 +0200 Subject: [PATCH 0092/1068] Rename `PermalinkUseCase` to `PermalinkFactory` --- .../im/vector/app/features/home/HomeDrawerFragment.kt | 6 +++--- .../app/features/home/room/detail/TimelineFragment.kt | 10 +++++----- .../{PermalinkUseCase.kt => PermalinkFactory.kt} | 2 +- .../app/features/roomdirectory/PublicRoomsFragment.kt | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) rename vector/src/main/java/im/vector/app/features/permalink/{PermalinkUseCase.kt => PermalinkFactory.kt} (96%) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index b75d5c2e99..22d9709229 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -32,7 +32,7 @@ import im.vector.app.core.resources.BuildMeta import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.databinding.FragmentHomeDrawerBinding import im.vector.app.features.analytics.plan.MobileScreen -import im.vector.app.features.permalink.PermalinkUseCase +import im.vector.app.features.permalink.PermalinkFactory import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.spaces.SpaceListFragment @@ -50,7 +50,7 @@ class HomeDrawerFragment : @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var buildMeta: BuildMeta - @Inject lateinit var permalinkUseCase: PermalinkUseCase + @Inject lateinit var permalinkFactory: PermalinkFactory private lateinit var sharedActionViewModel: HomeSharedActionViewModel @@ -103,7 +103,7 @@ class HomeDrawerFragment : } views.homeDrawerInviteFriendButton.debouncedClicks { - permalinkUseCase.createPermalinkOfCurrentUser()?.let { permalink -> + permalinkFactory.createPermalinkOfCurrentUser()?.let { permalink -> analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends)) val text = getString(R.string.invite_friends_text, permalink) 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 b17cefa6b7..59af00fac3 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 @@ -169,7 +169,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler -import im.vector.app.features.permalink.PermalinkUseCase +import im.vector.app.features.permalink.PermalinkFactory import im.vector.app.features.poll.PollMode import im.vector.app.features.reactions.EmojiReactionPickerActivity import im.vector.app.features.roomprofile.RoomProfileActivity @@ -248,7 +248,7 @@ class TimelineFragment : @Inject lateinit var clock: Clock @Inject lateinit var vectorFeatures: VectorFeatures @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory - @Inject lateinit var permalinkUseCase: PermalinkUseCase + @Inject lateinit var permalinkFactory: PermalinkFactory companion object { const val MAX_TYPING_MESSAGE_USERS_COUNT = 4 @@ -869,7 +869,7 @@ class TimelineFragment : } R.id.menu_thread_timeline_copy_link -> { getRootThreadEventId()?.let { - val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, it) + val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, it) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } @@ -881,7 +881,7 @@ class TimelineFragment : } R.id.menu_thread_timeline_share -> { getRootThreadEventId()?.let { - val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, it) + val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, it) shareText(requireContext(), permalink) } true @@ -1790,7 +1790,7 @@ class TimelineFragment : } } is EventSharedAction.CopyPermalink -> { - val permalink = permalinkUseCase.createPermalink(timelineArgs.roomId, action.eventId) + val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt rename to vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt index d9c629d5d0..378ffc4569 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt @@ -22,7 +22,7 @@ import javax.inject.Inject /** * Contains synchronous methods to create permalinks from the Session. */ -class PermalinkUseCase @Inject constructor( +class PermalinkFactory @Inject constructor( private val session: Session, ) { fun createPermalinkOfCurrentUser(): String? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index 89ee7434df..c7d799346b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -38,7 +38,7 @@ import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler -import im.vector.app.features.permalink.PermalinkUseCase +import im.vector.app.features.permalink.PermalinkFactory import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -60,7 +60,7 @@ class PublicRoomsFragment : @Inject lateinit var publicRoomsController: PublicRoomsController @Inject lateinit var permalinkHandler: PermalinkHandler - @Inject lateinit var permalinkUseCase: PermalinkUseCase + @Inject lateinit var permalinkFactory: PermalinkFactory private val viewModel: RoomDirectoryViewModel by activityViewModel() private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel @@ -128,7 +128,7 @@ class PublicRoomsFragment : override fun onUnknownRoomClicked(roomIdOrAlias: String) { viewLifecycleOwner.lifecycleScope.launch { - val permalink = permalinkUseCase.createPermalink(roomIdOrAlias) + val permalink = permalinkFactory.createPermalink(roomIdOrAlias) val isHandled = permalinkHandler .launch(requireActivity(), permalink, object : NavigationInterceptor { override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { From 822f06fef4c6ca24d5e7c5f4f62273529c119610 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 7 Oct 2022 14:57:06 +0200 Subject: [PATCH 0093/1068] Fix ktlint issue --- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +- .../im/vector/app/features/roomdirectory/PublicRoomsFragment.kt | 2 +- 2 files changed, 2 insertions(+), 2 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 59af00fac3..9d50cdb070 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 @@ -168,8 +168,8 @@ import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.permalink.NavigationInterceptor -import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkFactory +import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.poll.PollMode import im.vector.app.features.reactions.EmojiReactionPickerActivity import im.vector.app.features.roomprofile.RoomProfileActivity diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index c7d799346b..e541e7f879 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -37,8 +37,8 @@ import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.permalink.NavigationInterceptor -import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkFactory +import im.vector.app.features.permalink.PermalinkHandler import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach From fa1d2bd8ab9cef1015cd83afbb2b42c246f15e0c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Oct 2022 15:12:41 +0200 Subject: [PATCH 0094/1068] Fix compilation issue after rebase. --- .../im/vector/app/features/settings/VectorSettingsActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt index dcf8e7b3ae..4a9db49c67 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt @@ -63,8 +63,6 @@ class VectorSettingsActivity : VectorBaseActivity @Inject lateinit var session: Session - @Inject lateinit var vectorPreferences: VectorPreferences - override fun initUiAndData() { setupToolbar(views.settingsToolbar) .allowBack() From 3864d937d93a89628e146c77c565959925f71b65 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 17 Oct 2022 15:52:55 +0200 Subject: [PATCH 0095/1068] Set warnWhenNotUsingParallelGC to false, because it fails the build. --- tools/gradle/doctor.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/gradle/doctor.gradle b/tools/gradle/doctor.gradle index 705bb3e250..7a7adad062 100644 --- a/tools/gradle/doctor.gradle +++ b/tools/gradle/doctor.gradle @@ -54,7 +54,8 @@ doctor { /** * Warn when not using parallel GC. Parallel GC is faster for build type tasks and is no longer the default in Java 9+. */ - warnWhenNotUsingParallelGC = true + // Note: Actually, if set to true, it fails the build. See https://lightrun.com/answers/runningcode-gradle-doctor-warnwhennotusingparallelgc-fails-the-build-warn-is-a-confusing-keyword-here + warnWhenNotUsingParallelGC = false /** * Throws an error when the `Delete` or `clean` task has dependencies. * If a clean task depends on other tasks, clean can be reordered and made to run after the tasks that would produce From e01ee619d3bb59d5bec0c1d0ee1bef9829584f40 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:02:25 +0100 Subject: [PATCH 0096/1068] Refactor error handling and report E2EE errors --- .../src/main/res/values/strings.xml | 6 + .../android/sdk/api/rendezvous/Rendezvous.kt | 140 ++++++++++-------- .../sdk/api/rendezvous/RendezvousChannel.kt | 12 +- .../api/rendezvous/RendezvousFailureReason.kt | 25 ++-- .../sdk/api/rendezvous/RendezvousTransport.kt | 7 +- .../channels/ECDHRendezvousChannel.kt | 27 +--- .../sdk/api/rendezvous/model/Outcome.kt | 8 +- .../api/rendezvous/model/RendezvousError.kt | 2 +- .../SimpleHttpRendezvousTransport.kt | 57 ++++--- .../login/qr/QrCodeLoginStatusFragment.kt | 6 + .../features/login/qr/QrCodeLoginViewModel.kt | 27 +++- 11 files changed, 171 insertions(+), 146 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7d0173f341..e28ebb31c6 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3383,6 +3383,12 @@ The linking wasn’t completed in the required time. The request was denied on the other device. The request failed. + A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your intent connection(s); Your device(s); + The other device is already signed in. + The other device must be signed in. + The QR code scanned is invalid. + The sign in was cancelled on the other device. + The homeserver doesn\'t support sign in with QR code. Open ${app_name} on your other device Go to Settings -> Security & Privacy -> Show All Sessions Select \'Show QR code in this device\' diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index e467ff06e3..9ad889fca0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.rendezvous.model.Outcome import org.matrix.android.sdk.api.rendezvous.model.Payload import org.matrix.android.sdk.api.rendezvous.model.PayloadType import org.matrix.android.sdk.api.rendezvous.model.Protocol +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport import org.matrix.android.sdk.api.session.Session @@ -47,10 +48,16 @@ class Rendezvous( companion object { private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value - fun buildChannelFromCode(code: String, onCancelled: (reason: RendezvousFailureReason) -> Unit): Rendezvous { - val parsed = MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) ?: throw RuntimeException("Invalid code") + @Throws(RendezvousError::class) + fun buildChannelFromCode(code: String): Rendezvous { + val parsed = try { + // we rely on moshi validating the code and throwing exception if invalid JSON or doesn't + MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) + } catch (a: Throwable) { + throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode) + } ?: throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode) - val transport = SimpleHttpRendezvousTransport(onCancelled, parsed.rendezvous.transport.uri) + val transport = SimpleHttpRendezvousTransport(parsed.rendezvous.transport.uri) return Rendezvous( ECDHRendezvousChannel(transport, parsed.rendezvous.key), @@ -64,32 +71,30 @@ class Rendezvous( // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE - private suspend fun areIntentsIncompatible(): Boolean { + @Throws(RendezvousError::class) + private suspend fun checkCompatibility() { val incompatible = theirIntent == ourIntent Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible") if (incompatible) { + // inform the other side send(Payload(PayloadType.FINISH, intent = ourIntent)) - val reason = if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { - RendezvousFailureReason.OtherDeviceNotSignedIn + if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { + throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn) } else { - RendezvousFailureReason.OtherDeviceAlreadySignedIn + throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn) } - channel.cancel(reason) } - - return incompatible } + @Throws(RendezvousError::class) suspend fun startAfterScanningCode(): String? { val checksum = channel.connect() Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum") - if (areIntentsIncompatible()) { - return null - } + checkCompatibility() // get protocols Timber.tag(TAG).i("Waiting for protocols") @@ -97,9 +102,7 @@ class Rendezvous( if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) { send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED)) - Timber.tag(TAG).i("No supported protocol") - cancel(RendezvousFailureReason.Unknown) - return null + throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver) } send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN)) @@ -107,6 +110,7 @@ class Rendezvous( return checksum } + @Throws(RendezvousError::class) suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { Timber.tag(TAG).i("Waiting for login_token") @@ -115,24 +119,19 @@ class Rendezvous( if (loginToken?.type == PayloadType.FINISH) { when (loginToken.outcome) { Outcome.DECLINED -> { - Timber.tag(TAG).i("Login declined by other device") - channel.cancel(RendezvousFailureReason.UserDeclined) - return null + throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined) } Outcome.UNSUPPORTED -> { - Timber.tag(TAG).i("Not supported") - channel.cancel(RendezvousFailureReason.HomeserverLacksSupport) - return null + throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver) } else -> { - channel.cancel(RendezvousFailureReason.Unknown) - return null + throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown) } } } - val homeserver = loginToken?.homeserver ?: throw RuntimeException("No homeserver returned") - val token = loginToken.loginToken ?: throw RuntimeException("No login token returned") + val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError) + val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError) Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver") @@ -140,6 +139,7 @@ class Rendezvous( return authenticationService.loginUsingQrLoginToken(hsConfig, token) } + @Throws(RendezvousError::class) suspend fun completeVerificationOnNewDevice(session: Session) { val userId = session.myUserId val crypto = session.cryptoService() @@ -148,59 +148,77 @@ class Rendezvous( send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) // await confirmation of verification - val verificationResponse = receive() - val verifyingDeviceId = verificationResponse?.verifyingDeviceId ?: throw RuntimeException("No verifying device id returned") - val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) - if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { - Timber.tag(TAG).w( - "Verifying device $verifyingDeviceId key doesn't match: ${ - verifyingDeviceFromServer?.fingerprint()} vs ${verificationResponse.verifyingDeviceKey})" - ) - throw RuntimeException("Key from verifying device doesn't match") - } + if (verificationResponse?.outcome == Outcome.VERIFIED) { + val verifyingDeviceId = verificationResponse.verifyingDeviceId + ?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError) + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { + Timber.tag(TAG).w( + "Verifying device $verifyingDeviceId key doesn't match: ${ + verifyingDeviceFromServer?.fingerprint() + } vs ${verificationResponse.verifyingDeviceKey})" + ) + // inform the other side + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } - // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice -> + // check master key againt what the homeserver told us + crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> + if (localMasterKey.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { + Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") + // inform the other side + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - verificationResponse.masterKey ?.let { masterKeyFromVerifyingDevice -> - // set master key as trusted - crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> - if (localMasterKey.unpaddedBase64PublicKey == masterKeyFromVerifyingDevice) { Timber.tag(TAG).i("Setting master key as trusted") crypto.crossSigningService().markMyMasterKeyAsTrusted() - } else { - Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") - throw RuntimeException("Master key from verifying device doesn't match") - } - } ?: Timber.tag(TAG).i("No local master key") - } ?: Timber.tag(TAG).i("No master key given by verifying device") + } ?: Timber.tag(TAG).w("No local master key so not verifying") + } ?: run { + // set other device as verified anyway + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) - // request secrets from the verifying device - Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + Timber.tag(TAG).i("No master key given by verifying device") + } - session.sharedSecretStorageService().let { - it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) - it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + + session.sharedSecretStorageService().let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + } + } else { + Timber.tag(TAG).i("Not doing verification") } } + @Throws(RendezvousError::class) private suspend fun receive(): Payload? { val data = channel.receive() ?: return null - return adapter.fromJson(data.toString(Charsets.UTF_8)) + val payload = try { + adapter.fromJson(data.toString(Charsets.UTF_8)) + } catch (e: Exception) { + Timber.tag(TAG).w(e, "Failed to parse payload") + throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown) + } + + return payload } private suspend fun send(payload: Payload) { channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8)) } - suspend fun cancel(reason: RendezvousFailureReason) { - channel.cancel(reason) - } - suspend fun close() { channel.close() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 31014e392d..be79569164 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -16,8 +16,7 @@ package org.matrix.android.sdk.api.rendezvous -import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode -import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError /** * Representation of a rendezvous channel such as that described by MSC3903. @@ -28,26 +27,25 @@ interface RendezvousChannel { /** * @returns the checksum/confirmation digits to be shown to the user */ + @Throws(RendezvousError::class) suspend fun connect(): String /** * Send a payload via the channel. * @param data payload to send */ + @Throws(RendezvousError::class) suspend fun send(data: ByteArray) /** * Receive a payload from the channel. * @returns the received payload */ + @Throws(RendezvousError::class) suspend fun receive(): ByteArray? /** - * @returns a representation of the channel that can be encoded in a QR or similar + * @returns closes the channel and cleans up */ suspend fun close() - - // In future we probably want this to be a more generic RendezvousCode but it is suffice for now - suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode - suspend fun cancel(reason: RendezvousFailureReason) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt index a607dc7f38..18e625d825 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt @@ -16,16 +16,17 @@ package org.matrix.android.sdk.api.rendezvous -enum class RendezvousFailureReason(val value: String, val canRetry: Boolean = true) { - UserDeclined("user_declined"), - OtherDeviceNotSignedIn("other_device_not_signed_in"), - OtherDeviceAlreadySignedIn("other_device_already_signed_in"), - Unknown("unknown"), - Expired("expired"), - UserCancelled("user_cancelled"), - InvalidCode("invalid_code"), - UnsupportedAlgorithm("unsupported_algorithm", false), - DataMismatch("data_mismatch"), - UnsupportedTransport("unsupported_transport", false), - HomeserverLacksSupport("homeserver_lacks_support", false) +enum class RendezvousFailureReason(val canRetry: Boolean = true) { + UserDeclined, + OtherDeviceNotSignedIn, + OtherDeviceAlreadySignedIn, + Unknown, + Expired, + UserCancelled, + InvalidCode, + UnsupportedAlgorithm(false), + UnsupportedTransport(false), + UnsupportedHomeserver(false), + ProtocolError, + E2EESecurityIssue(false) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt index de0aed7efc..5daf906930 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt @@ -17,13 +17,16 @@ package org.matrix.android.sdk.api.rendezvous import okhttp3.MediaType +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails interface RendezvousTransport { var ready: Boolean - var onCancelled: ((reason: RendezvousFailureReason) -> Unit)? + @Throws(RendezvousError::class) suspend fun details(): RendezvousTransportDetails + @Throws(RendezvousError::class) suspend fun send(contentType: MediaType, data: ByteArray) + @Throws(RendezvousError::class) suspend fun receive(): ByteArray? - suspend fun cancel(reason: RendezvousFailureReason) + suspend fun close() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index ca7083b297..9a5c5e865a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -89,13 +89,14 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP) } + @Throws(RendezvousError::class) override suspend fun connect(): String { olmSAS ?.let { olmSAS -> val isInitiator = theirPublicKey == null if (isInitiator) { Timber.tag(TAG).i("Waiting for other device to send their public key") - val res = this.receiveAsPayload() ?: throw RuntimeException("No reply from other device") + val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError) if (res.key == null) { throw RendezvousError( @@ -137,7 +138,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu override suspend fun send(data: ByteArray) { if (aesKey == null) { - throw RuntimeException("Shared secret not established") + throw IllegalStateException("Shared secret not established") } send(encrypt(data)) } @@ -150,31 +151,12 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu override suspend fun receive(): ByteArray? { if (aesKey == null) { - throw RuntimeException("Shared secret not established") + throw IllegalStateException("Shared secret not established") } val payload = receiveAsPayload() ?: return null return decrypt(payload) } - override suspend fun generateCode(intent: RendezvousIntent): ECDHRendezvousCode { - return ECDHRendezvousCode( - intent, - rendezvous = ECDHRendezvous( - transport.details() as SimpleHttpRendezvousTransportDetails, - SecureRendezvousChannelAlgorithm.ECDH_V1, - key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) - ) - ) - } - - override suspend fun cancel(reason: RendezvousFailureReason) { - try { - transport.cancel(reason) - } finally { - close() - } - } - override suspend fun close() { olmSAS ?.let { synchronized(it) { @@ -183,6 +165,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu olmSAS = null } } + transport.close() } private fun encrypt(plainText: ByteArray): ECDHPayload { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt index 2dd6e7be28..2ef24e9cb7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt @@ -26,5 +26,11 @@ enum class Outcome(val value: String) { DECLINED("declined"), @Json(name = "unsupported") - UNSUPPORTED("unsupported") + UNSUPPORTED("unsupported"), + + @Json(name = "verified") + VERIFIED("verified"), + + @Json(name = "e2ee_security_error") + E2EE_SECURITY_ERROR("e2ee_security_error") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt index fec55ffb67..c52b11a322 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt @@ -18,4 +18,4 @@ package org.matrix.android.sdk.api.rendezvous.model import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason -class RendezvousError(val description: String, val reason: RendezvousFailureReason) : RuntimeException(description) +class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index ca2a3425cd..fa0e5d8e2a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -23,6 +23,7 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.api.rendezvous.RendezvousTransport +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails import timber.log.Timber @@ -32,7 +33,7 @@ import java.util.Date /** * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 */ -class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: RendezvousFailureReason) -> Unit)?, rendezvousUri: String?) : RendezvousTransport { +class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport { companion object { private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value } @@ -55,7 +56,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo override suspend fun send(contentType: MediaType, data: ByteArray) { if (cancelled) { - return + throw IllegalStateException("Rendezvous cancelled") } val method = if (uri != null) "PUT" else "POST" @@ -75,9 +76,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo val response = httpClient.newCall(request.build()).execute() if (response.code == 404) { - // we set to unknown and the cancel method will rewrite the reason to expired if applicable - cancel(RendezvousFailureReason.Unknown) - return + throw get404Error() } etag = response.header("etag") @@ -98,12 +97,12 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo } override suspend fun receive(): ByteArray? { + if (cancelled) { + throw IllegalStateException("Rendezvous cancelled") + } val uri = uri ?: throw IllegalStateException("Rendezvous not set up") val httpClient = okhttp3.OkHttpClient.Builder().build() while (true) { - if (cancelled) { - return null - } Timber.tag(TAG).i("Polling: $uri after etag $etag") val request = Request.Builder() .url(uri) @@ -118,9 +117,7 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo try { // expired if (response.code == 404) { - // we set to unknown and the cancel method will rewrite the reason to expired if applicable - cancel(RendezvousFailureReason.Unknown) - return null + throw get404Error() } // rely on server expiring the channel rather than checking ourselves @@ -145,31 +142,27 @@ class SimpleHttpRendezvousTransport(override var onCancelled: ((reason: Rendezvo } } - override suspend fun cancel(reason: RendezvousFailureReason) { - var mappedReason = reason - Timber.tag(TAG).i("$expiresAt") - if (mappedReason == RendezvousFailureReason.Unknown && - expiresAt != null && Date() > expiresAt - ) { - mappedReason = RendezvousFailureReason.Expired - } + private fun get404Error(): RendezvousError { + return if (expiresAt != null && Date() > expiresAt) + RendezvousError("Expired", RendezvousFailureReason.Expired) + else + RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown) + } + override suspend fun close() { cancelled = true ready = false - onCancelled ?.let { it(mappedReason) } - if (mappedReason == RendezvousFailureReason.UserDeclined) { - uri ?.let { - try { - val httpClient = okhttp3.OkHttpClient.Builder().build() - val request = Request.Builder() - .url(it) - .delete() - .build() - httpClient.newCall(request).execute() - } catch (e: Exception) { - Timber.tag(TAG).w(e, "Failed to delete channel") - } + uri ?.let { + try { + val httpClient = okhttp3.OkHttpClient.Builder().build() + val request = Request.Builder() + .url(it) + .delete() + .build() + httpClient.newCall(request).execute() + } catch (e: Throwable) { + Timber.tag(TAG).w(e, "Failed to delete channel") } } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 0691e2367e..617d620c27 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -70,8 +70,14 @@ class QrCodeLoginStatusFragment : VectorBaseFragment getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) + RendezvousFailureReason.UnsupportedHomeserver -> getString(R.string.qr_code_login_header_failed_homeserver_is_not_supported_description) RendezvousFailureReason.Expired -> getString(R.string.qr_code_login_header_failed_timeout_description) RendezvousFailureReason.UserDeclined -> getString(R.string.qr_code_login_header_failed_denied_description) + RendezvousFailureReason.E2EESecurityIssue -> getString(R.string.qr_code_login_header_failed_e2ee_security_issue_description) + RendezvousFailureReason.OtherDeviceAlreadySignedIn -> getString(R.string.qr_code_login_header_failed_other_device_already_signed_in_description) + RendezvousFailureReason.OtherDeviceNotSignedIn -> getString(R.string.qr_code_login_header_failed_other_device_not_signed_in_description) + RendezvousFailureReason.InvalidCode -> getString(R.string.qr_code_login_header_failed_invalid_qr_code_description) + RendezvousFailureReason.UserCancelled -> getString(R.string.qr_code_login_header_failed_user_cancelled_description) else -> getString(R.string.qr_code_login_header_failed_other_description) } } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 9d4e5e9870..7f95fad485 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.rendezvous.Rendezvous import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import timber.log.Timber class QrCodeLoginViewModel @AssistedInject constructor( @@ -38,14 +39,15 @@ class QrCodeLoginViewModel @AssistedInject constructor( private val activeSessionHolder: ActiveSessionHolder, private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase ) : VectorViewModel(initialState) { - val TAG: String = QrCodeLoginViewModel::class.java.simpleName @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + val TAG: String = QrCodeLoginViewModel::class.java.simpleName + } override fun handle(action: QrCodeLoginAction) { when (action) { @@ -71,9 +73,14 @@ class QrCodeLoginViewModel @AssistedInject constructor( private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) { Timber.tag(TAG).d("Scanned code: ${action.qrCode}") - val rendezvous = Rendezvous.buildChannelFromCode(action.qrCode) { reason -> - Timber.tag(TAG).d("Rendezvous cancelled: $reason") - onFailed(reason) + val rendezvous = try { Rendezvous.buildChannelFromCode(action.qrCode) } catch (t: Throwable) { + Timber.tag(TAG).e(t, "Error occurred during sign in") + if (t is RendezvousError) { + onFailed(t.reason) + } else { + onFailed(RendezvousFailureReason.Unknown) + } + return } setState { @@ -103,9 +110,13 @@ class QrCodeLoginViewModel @AssistedInject constructor( _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) } } - } catch (failure: Throwable) { - Timber.tag(TAG).e(failure, "Error occurred during sign in") - onFailed(RendezvousFailureReason.Unknown) + } catch (t: Throwable) { + Timber.tag(TAG).e(t, "Error occurred during sign in") + if (t is RendezvousError) { + onFailed(t.reason) + } else { + onFailed(RendezvousFailureReason.Unknown) + } } } } From 29065b819f6be245a9e78ca3ba7666766d0ce28a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:03:47 +0100 Subject: [PATCH 0097/1068] Remove unused class --- .../rendezvous/model/EmbeddedRendezvous.kt | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt deleted file mode 100644 index 785ce1fed7..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/EmbeddedRendezvous.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.rendezvous.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -open class EmbeddedRendezvous( - @Json(name = "transport") val transport: RendezvousTransportDetails, - @Json(name = "algorithm") val algorithm: SecureRendezvousChannelAlgorithm -) From 76a11d97dc61a42a1deac01f20e9f22c95ac0c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 17 Oct 2022 17:05:36 +0200 Subject: [PATCH 0098/1068] Bump WYSIWYG lib to 0.2.1 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index d60283e825..547b76aa89 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -101,7 +101,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.1.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.2.1" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From e877feed6eedc22792a24188e3fa37099d5f1f93 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:06:08 +0100 Subject: [PATCH 0099/1068] Add @JsonClass to all enums --- .../java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt | 2 ++ .../org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt | 2 ++ .../org/matrix/android/sdk/api/rendezvous/model/Protocol.kt | 2 ++ .../matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt | 2 ++ .../android/sdk/api/rendezvous/model/RendezvousTransportType.kt | 2 ++ .../api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt | 2 ++ 6 files changed, 12 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt index 2ef24e9cb7..a0b2cb31b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) enum class Outcome(val value: String) { @Json(name = "success") SUCCESS("success"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt index 5ff4cd7cfa..9e0dbd9562 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) internal enum class PayloadType(val value: String) { @Json(name = "m.login.start") START("m.login.start"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt index 18381984a5..3442d4abf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) enum class Protocol(val value: String) { @Json(name = "login_token") LOGIN_TOKEN("login_token") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt index 1c070599b0..8ce6430212 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) enum class RendezvousIntent { @Json(name = "login.start") LOGIN_ON_NEW_DEVICE, @Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt index 9c3e44f25b..2814cad77c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) enum class RendezvousTransportType(val value: String) { @Json(name = "http.v1") MSC3886_SIMPLE_HTTP_V1("http.v1") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt index 9a9db58a41..d9f73a0b99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -17,7 +17,9 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) enum class SecureRendezvousChannelAlgorithm(val value: String) { @Json(name = "m.rendezvous.v1.curve25519-aes-sha256") ECDH_V1("m.rendezvous.v1.curve25519-aes-sha256") } From 7793667970ae4b000d7808ce353d03cdaf6c8d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 17 Oct 2022 17:12:16 +0200 Subject: [PATCH 0100/1068] Update changelog --- changelog.d/7384.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7384.misc diff --git a/changelog.d/7384.misc b/changelog.d/7384.misc new file mode 100644 index 0000000000..3994dc0fa1 --- /dev/null +++ b/changelog.d/7384.misc @@ -0,0 +1 @@ +Update WYSIWYG library to v0.2.1. From 623277e31ff94a4deac05b3bc297b95f52c85441 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:13:49 +0100 Subject: [PATCH 0101/1068] Lint --- .../sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt | 4 ---- .../transports/SimpleHttpRendezvousTransport.kt | 9 +++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 9a5c5e865a..2f368d6520 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -24,12 +24,8 @@ import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.RendezvousChannel import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason import org.matrix.android.sdk.api.rendezvous.RendezvousTransport -import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvous -import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode import org.matrix.android.sdk.api.rendezvous.model.RendezvousError -import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm -import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.internal.extensions.toUnsignedInt import org.matrix.olm.OlmSAS diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index fa0e5d8e2a..50cebae12d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -143,10 +143,11 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor } private fun get404Error(): RendezvousError { - return if (expiresAt != null && Date() > expiresAt) - RendezvousError("Expired", RendezvousFailureReason.Expired) - else - RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown) + if (expiresAt != null && Date() > expiresAt) { + return RendezvousError("Expired", RendezvousFailureReason.Expired) + } + + return RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown) } override suspend fun close() { From 3d37e0b2a557cf121efe01935a5a2980c9177ff2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:19:03 +0100 Subject: [PATCH 0102/1068] Fix enum JsonClass generateAdapter = false --- .../java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt | 2 +- .../org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt | 2 +- .../org/matrix/android/sdk/api/rendezvous/model/Protocol.kt | 2 +- .../matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt | 2 +- .../android/sdk/api/rendezvous/model/RendezvousTransportType.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt index a0b2cb31b4..0ebd1f88b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) enum class Outcome(val value: String) { @Json(name = "success") SUCCESS("success"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt index 9e0dbd9562..33beb1f525 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) internal enum class PayloadType(val value: String) { @Json(name = "m.login.start") START("m.login.start"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt index 3442d4abf4..7c020da641 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) enum class Protocol(val value: String) { @Json(name = "login_token") LOGIN_TOKEN("login_token") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt index 8ce6430212..65037e1252 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) enum class RendezvousIntent { @Json(name = "login.start") LOGIN_ON_NEW_DEVICE, @Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt index 2814cad77c..aa578e3660 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) enum class RendezvousTransportType(val value: String) { @Json(name = "http.v1") MSC3886_SIMPLE_HTTP_V1("http.v1") } From 552fb9de9a30d8295256329f0c63fc525d1031cb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:24:01 +0100 Subject: [PATCH 0103/1068] Improved comment around QR generation --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 7f95fad485..69deb44c70 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -147,9 +147,10 @@ class QrCodeLoginViewModel @AssistedInject constructor( } /** - * TODO. UI test purpose. Fixme accordingly. + * QR code generation is not currently supported and this is a placeholder for future + * functionality. */ private fun generateQrCodeData(): String { - return "TODO" + return "NOT SUPPORTED" } } From b2dc0b33b5fb07bf47a5cc697589479d138e5515 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 17 Oct 2022 18:32:35 +0300 Subject: [PATCH 0104/1068] Implement try again button action. --- .../GetHomeServerCapabilitiesTask.kt | 1 + .../features/login/qr/QrCodeLoginAction.kt | 1 + .../features/login/qr/QrCodeLoginActivity.kt | 44 +++++++++++-------- .../login/qr/QrCodeLoginStatusFragment.kt | 7 +++ .../login/qr/QrCodeLoginViewEvents.kt | 1 + .../features/login/qr/QrCodeLoginViewModel.kt | 5 +++ 6 files changed, 41 insertions(+), 18 deletions(-) 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 2c3cb440b6..4b56d8e756 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.getServerName 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 diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt index 8854d0720f..5ea46d3dcd 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt @@ -22,4 +22,5 @@ sealed class QrCodeLoginAction : VectorViewModelAction { data class OnQrCodeScanned(val qrCode: String) : QrCodeLoginAction() object GenerateQrCode : QrCodeLoginAction() object ShowQrCode : QrCodeLoginAction() + object TryAgain : QrCodeLoginAction() } diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt index 2f30261890..c9b8ae0080 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt @@ -38,30 +38,33 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { super.onCreate(savedInstanceState) views.toolbar.visibility = View.GONE - val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) if (isFirstCreation()) { - when (qrCodeLoginArgs?.loginType) { - QrCodeLoginType.LOGIN -> { - showInstructionsFragment(qrCodeLoginArgs) - } - QrCodeLoginType.LINK_A_DEVICE -> { - if (qrCodeLoginArgs.showQrCodeImmediately) { - handleNavigateToShowQrCodeScreen() - } else { - showInstructionsFragment(qrCodeLoginArgs) - } - } - null -> { - Timber.i("QrCodeLoginArgs is null. This is not expected.") - finish() - return - } - } + navigateToInitialFragment() } observeViewEvents() } + private fun navigateToInitialFragment() { + val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) + when (qrCodeLoginArgs?.loginType) { + QrCodeLoginType.LOGIN -> { + showInstructionsFragment(qrCodeLoginArgs) + } + QrCodeLoginType.LINK_A_DEVICE -> { + if (qrCodeLoginArgs.showQrCodeImmediately) { + handleNavigateToShowQrCodeScreen() + } else { + showInstructionsFragment(qrCodeLoginArgs) + } + } + null -> { + Timber.i("QrCodeLoginArgs is null. This is not expected.") + finish() + } + } + } + private fun showInstructionsFragment(qrCodeLoginArgs: QrCodeLoginArgs) { addFragment( views.container, @@ -77,10 +80,15 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen() QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen() QrCodeLoginViewEvents.NavigateToHomeScreen -> handleNavigateToHomeScreen() + QrCodeLoginViewEvents.NavigateToInitialScreen -> handleNavigateToInitialScreen() } } } + private fun handleNavigateToInitialScreen() { + navigateToInitialFragment() + } + private fun handleNavigateToShowQrCodeScreen() { addFragment( views.container, diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 617d620c27..097c956f2c 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -42,6 +42,13 @@ class QrCodeLoginStatusFragment : VectorBaseFragment handleOnQrCodeScanned(action) QrCodeLoginAction.GenerateQrCode -> handleQrCodeViewStarted() QrCodeLoginAction.ShowQrCode -> handleShowQrCode() + QrCodeLoginAction.TryAgain -> handleTryAgain() } } + private fun handleTryAgain() { + _viewEvents.post(QrCodeLoginViewEvents.NavigateToInitialScreen) + } + private fun handleShowQrCode() { _viewEvents.post(QrCodeLoginViewEvents.NavigateToShowQrCodeScreen) } From 1863e4c3efe867faa0e30d001fadf33885fbe08d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 16:34:51 +0100 Subject: [PATCH 0105/1068] Use unstable prefixes --- .../org/matrix/android/sdk/api/rendezvous/model/Protocol.kt | 4 ++-- .../sdk/api/rendezvous/model/RendezvousTransportType.kt | 3 ++- .../api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt index 7c020da641..6fce2fa11c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt @@ -21,6 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) enum class Protocol(val value: String) { - @Json(name = "login_token") - LOGIN_TOKEN("login_token") + @Json(name = "org.matrix.msc3906.login_token") + LOGIN_TOKEN("org.matrix.msc3906.login_token") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt index aa578e3660..6fca7efa71 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = false) enum class RendezvousTransportType(val value: String) { - @Json(name = "http.v1") MSC3886_SIMPLE_HTTP_V1("http.v1") + @Json(name = "org.matrix.msc3886.http.v1") + MSC3886_SIMPLE_HTTP_V1("org.matrix.msc3886.http.v1") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt index d9f73a0b99..ab58179f5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) enum class SecureRendezvousChannelAlgorithm(val value: String) { - @Json(name = "m.rendezvous.v1.curve25519-aes-sha256") ECDH_V1("m.rendezvous.v1.curve25519-aes-sha256") + @Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") + ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") } From eec99e65bde304e5da4882799188c4f3d6776b72 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 17 Oct 2022 16:48:02 +0200 Subject: [PATCH 0106/1068] thread read receipts and unread notifications support is added to homeserver capatibilities --- .../homeserver/HomeServerCapabilities.kt | 5 +++ .../auth/version/HomeServerVersion.kt | 1 + .../sdk/internal/auth/version/Versions.kt | 12 +++++++ .../database/RealmSessionStoreMigration.kt | 4 ++- .../mapper/HomeServerCapabilitiesMapper.kt | 1 + .../database/migration/MigrateSessionTo040.kt | 34 +++++++++++++++++++ .../model/HomeServerCapabilitiesEntity.kt | 1 + .../GetHomeServerCapabilitiesTask.kt | 3 ++ 8 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt 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 8c14ca892a..773e870ffd 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 @@ -65,6 +65,11 @@ data class HomeServerCapabilities( * True if the home server supports login via qr code, false otherwise. */ val canLoginWithQrCode: Boolean = false, + + /** + * True if the home server supports threaded read receipts and unread notifications. + */ + val canUseThreadReadReceiptsAndNotifications: Boolean = false, ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index 75639c6a21..d443d6e3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -60,5 +60,6 @@ internal data class HomeServerVersion( val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) + val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index 5e133fab9c..9ed8901683 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -54,6 +54,8 @@ private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind" private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440" private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable" private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" +private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" +private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" /** * Return true if the SDK supports this homeserver version. @@ -79,6 +81,16 @@ internal fun Versions.doesServerSupportThreads(): Boolean { return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false } +/** + * Indicate if the homeserver support MSC3771 and MSC3773 for threaded read receipts and unread notifications. + */ +internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean { + val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false + val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false + return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773) + +} + internal fun Versions.doesServerSupportQrCodeLogin(): Boolean { return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false } 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 9a2c32f97c..def0f6de7a 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 @@ -56,6 +56,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -64,7 +65,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 39L, + schemaVersion = 40L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -113,5 +114,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 37) MigrateSessionTo037(realm).perform() if (oldVersion < 38) MigrateSessionTo038(realm).perform() if (oldVersion < 39) MigrateSessionTo039(realm).perform() + if (oldVersion < 40) MigrateSessionTo040(realm).perform() } } 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 63fa101c45..3528ca0051 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 @@ -45,6 +45,7 @@ internal object HomeServerCapabilitiesMapper { canUseThreading = entity.canUseThreading, canControlLogoutDevices = entity.canControlLogoutDevices, canLoginWithQrCode = entity.canLoginWithQrCode, + canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt new file mode 100644 index 0000000000..b3e02342dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt @@ -0,0 +1,34 @@ +/* + * 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 org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo040(realm: DynamicRealm) : RealmMigrator(realm, 40) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, false) + } + ?.forceRefreshOfHomeServerCapabilities() + } +} 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 cfa02b2c74..89f1e50b30 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 @@ -32,6 +32,7 @@ internal open class HomeServerCapabilitiesEntity( var canUseThreading: Boolean = false, var canControlLogoutDevices: Boolean = false, var canLoginWithQrCode: Boolean = false, + var canUseThreadReadReceiptsAndNotifications: Boolean = false, ) : RealmObject() { companion object 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 2c3cb440b6..a5953d870c 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 @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin +import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity @@ -144,6 +145,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ getVersionResult.doesServerSupportThreads() + homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications = + getVersionResult.doesServerSupportThreadUnreadNotifications() homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin() } From f686299d344739a5e4c07a62887356fb7d83af6f Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 17 Oct 2022 06:59:54 +0000 Subject: [PATCH 0107/1068] Translated using Weblate (Czech) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- .../src/main/res/values-cs/strings.xml | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index ffa7123dae..bc88530c3d 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -962,10 +962,10 @@ Push pravidla Žádná push pravidla nejsou definována Žádné push brány nejsou registrovány - app_id: - push_key: - app_display_name: - session_name: + ID aplikace: + Klíč push: + Zobrazovaný název aplikace: + Název relace: Url: Formát: Hlas a video @@ -1136,7 +1136,7 @@ \n \nZastavit proces změny hesla\? Nastavit emailovou adresu - Nastavte emailovou adresu pro obnovu svého účtu. Později můžete volitelně dovolit lidem, které znáte, aby Vás podle emailu nalezli. + Nastavte e-mailovou adresu pro obnovení účtu. Později můžete volitelně povolit svým známým, aby vás podle této adresy nalezli. Email Email (volitelné) Dále @@ -2796,4 +2796,34 @@ ⚠ V této místnosti jsou neověřená zařízení, která nebudou schopna dešifrovat odeslané zprávy. Nikdy neodesílat šifrované zprávy do neověřených relací v této místnosti. Rozumím - + Použít podtržení + Použít přeškrtnutí + Použít tučný text + Použít kurzívu + Zaznamenávat název, verzi a url pro snadnější rozpoznání relací ve správci relací. + Povolit záznamenávání informací o klientu + Získejte lepší přehled a kontrolu nad všemi relacemi. + Použít nový správce relací + Operační systém + Model + Prohlížeč + URL + Verze + Název + Aplikace + Přijímat push oznámení v této relaci. + Push oznámení + Ověřením aktuální relace zjistíte stav ověření této relace. + Neznámý stav ověření + Zapnuto: + ID relace: + Něco se pokazilo. Zkontrolujte prosím síťové připojení a zkuste to znovu. + Udělit oprávnění + ${app_name} potřebuje oprávnění k zobrazování oznámení. +\nUdělte prosím toto oprávnění. + ${app_name} potřebuje oprávnění k zobrazování oznámení. Oznámení mohou zobrazovat vaše zprávy, pozvánky atd. +\n +\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech. + Vyzkoušejte rozšířený textový editor (textový režim již brzy) + Povolit rozšířený textový editor + \ No newline at end of file From cfd750e5d28165f259bf64f0d49a4b243abaabf7 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 17 Oct 2022 06:42:51 +0000 Subject: [PATCH 0108/1068] Translated using Weblate (German) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index da59f03e18..6c292329ef 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2741,4 +2741,6 @@ ⚠ Es befinden sich nicht verifizierte Geräte in diesem Raum. Sie werden deine Nachrichten nicht entschlüsseln können. Niemals verschlüsselte Nachrichten zu unverifizierten Sitzungen in diesem Raum senden. Verstanden - + Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus) + Aktiviere Rich-Text-Editor + \ No newline at end of file From edf3f2cd46f41099e5d9e2f4ca85191cf9dfc7d6 Mon Sep 17 00:00:00 2001 From: jucktnich Date: Sun, 16 Oct 2022 21:46:00 +0000 Subject: [PATCH 0109/1068] Translated using Weblate (German) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 6c292329ef..882f04ed24 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2743,4 +2743,5 @@ Verstanden Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus) Aktiviere Rich-Text-Editor + Browser \ No newline at end of file From 579eedd050222801893f26feaf8a11f52f58740b Mon Sep 17 00:00:00 2001 From: Vri Date: Sun, 16 Oct 2022 12:50:08 +0000 Subject: [PATCH 0110/1068] Translated using Weblate (German) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 882f04ed24..545bb3f45d 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -708,10 +708,10 @@ Unerwarteter Fehler Bist du sicher\? Wiederherstellungsschlüssel eingeben - Stelle Backup wieder her: + Stelle Sicherung wieder her: Historie entschlüsseln Von Sicherung wiederherstellen - Sicherung löschen + Lösche Sicherung Lösche Sicherung … Lösche Sicherung Präferenz der Benachrichtigungen nach Ereignis @@ -722,7 +722,7 @@ [%1$s] \nDieser Fehler ist außerhalb von ${app_name} passiert. Es gibt kein Google-Konto auf dem Gerät. Bitte füge ein Google-Konto hinzu. Verwaltung der Kryptoschlüssel - Schlüssel-Sicherung verwalten + Schlüsselsicherung verwalten 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. @@ -763,7 +763,7 @@ Schlüsselsicherung sollte bei allen Sitzungen aktiviert sein, um den Verlust verschlüsselter Nachrichten zu verhindern. Ich möchte meine verschlüsselten Nachrichten nicht Sichere Schlüssel … - Sicher\? + Bist du sicher\? Sicherung Alle verschlüsselten Nachrichten gehen verloren, wenn Du dich abmeldest ohne die Schlüssel gesichert zu haben. Bist du sicher, dass du dich abmelden möchtest\? @@ -783,7 +783,7 @@ Deine Schlüssel wurden gesichert. Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz - du kannst es benutzen um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst. \nVerwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) - Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort auf, wie z.B. einem Passwortmanager (oder Tresor) auf + Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) auf Ich habe eine Kopie angefertigt Teilen Verliere nie wieder verschlüsselte Nachrichten @@ -1495,11 +1495,11 @@ Grund für den Bann Bann des Benutzers aufheben Das Aufheben des Bannes wird dem Benutzer erlauben dem Raum wieder beizutreten. - Sicheres Backup - Backup einrichten - Backup zurücksetzen + Verschlüsselte Sicherung + Sicherung einrichten + Sicherung zurücksetzen Auf diesem Gerät einrichten - Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden. + Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst. 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 @@ -1512,9 +1512,9 @@ ANSICHT Aktive Widgets Der Sicherheitsschlüssel ist gespeichert worden. - Backup + Verschlüsselte Sicherung Absicherung gegen den Verlust verschlüsselter Nachrichten - Richte Backup ein + Sicherung einrichten Nachricht entfernt Gelöschte Nachrichten zeigen Zeigt einen Platzhalter für gelöschte Nachrichten an @@ -1534,7 +1534,7 @@ Gib die Adresse des Servers ein, den du benutzen möchtest Einloggen mit Matrix-ID Einloggen mit Matrix-ID - Wenn du einen Account auf einem Homeserver eingerichtet hast, benutze deine Matrix-ID (z.B. @benutzer:domain.com) und Passwort. + Falls du ein Konto auf einem Heim-Server eingerichtet hast, verwende nachstehend deine Matrix-ID (z. B. @benutzer:domain.com) und dein Passwort. Matrix-ID Wenn du dein Passwort nicht weißt, gehe zurück um es zurücksetzen zu lassen. Dies ist keine gültige Benutzerkennung. Erwartetes Format: \'@benutzer:homeserver.org\' @@ -1547,7 +1547,7 @@ 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 und Daten verlieren. \n -\nDu kannst auch ein Backup einrichten und deine Schlüssel in den Einstellungen verwalten. +\nDu kannst auch eine Sicherung 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 @@ -1575,14 +1575,14 @@ Aktiviere Mikrophon Stoppe Kamera Starte Kamera - Backup - Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden. + Verschlüsselte Sicherung + Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst. Sicherheitsschlüssel benutzen - Generiere einen Sicherheitsschlüssel, welcher z.B. in einem Passwortmanager oder in einem Tresor sicher aufbewahrt werden sollte. + Generiere einen Sicherheitsschlüssel, den du in einem Passwort-Manager oder Tresor sicher aufbewahren solltest. Eine Sicherheitsphrase benutzen Gib eine geheime Phrase ein, die nur du kennst und generiere einen Schlüssel als Backup. Speichere deinen Sicherheitsschlüssel - Bewahre deinen Sicherheitsschlüssel irgendwo sicher auf, wie z.B. in einem Passwortmanager oder in einem Tresor. + Bewahre deinen Sicherheitsschlüssel in einem Passwort-Manager oder Tresor sicher auf. Sicherheitsphrase setzen Gib eine Sicherheitsphrase ein, welche nur du kennst und deine Daten auf dem Server geheim halten soll. Sicherheitsphrase @@ -1810,7 +1810,7 @@ Dieser Raum hat keine lokalen Adressen Füge Adressen für diesen Raum hinzu, damit andere Nutzer ihn auf %1$s finden können Lokale Adresse - Neue öffentliche Adresse (z.B. #alias:server) + Neue öffentliche Adresse (z. B. #alias:server) Noch keine weiteren öffentlichen Adressen vorhanden. Noch keine weiteren öffentlichen Adressen vorhanden, füge unten eine hinzu. Die Adresse \"%1$s\" löschen\? @@ -2744,4 +2744,25 @@ Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus) Aktiviere Rich-Text-Editor Browser + Durchgestrichen formatieren + Kursiv formatieren + Fett formatieren + Unterstrichen formatieren + ${app_name} benötigt die Berechtigung zur Anzeige von Benachrichtigungen. +\nBitte gewähre diese Berechtigung. + Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist. + Anwendungsinformationen erfassen + URL + Bessere Übersicht und Kontrolle über all deine Sitzungen. + Aktiviere neue Sitzungsverwaltung + Betriebssystem + Modell + Version + Name + Anwendung + Erhalte Push-Benachrichtigungen in dieser Sitzung. + Push-Benachrichtigungen + Verifiziere deine aktuelle Sitzung, um den Verifizierungsstatus dieser Sitzung anzuzeigen. + Unbekannter Verifizierungsstatus + Sitzungs-ID: \ No newline at end of file From 9cac1c8cfb66061ded81d87e3d8adbe37f0f4cb3 Mon Sep 17 00:00:00 2001 From: Christoph Klassen Date: Sat, 15 Oct 2022 12:21:02 +0000 Subject: [PATCH 0111/1068] Translated using Weblate (German) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 545bb3f45d..a8e24369b5 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2765,4 +2765,10 @@ Verifiziere deine aktuelle Sitzung, um den Verifizierungsstatus dieser Sitzung anzuzeigen. Unbekannter Verifizierungsstatus Sitzungs-ID: + Aktiviert: + Etwas ist schiefgelaufen. Bitte überprüfe deine Internetverbindung und versuche es erneut. + Berechtigung geben + ${app_name} braucht die Berechtigung, um Benachrichtigungen anzuzeigen. Benachrichtigungen können deine Nachrichten, Einladungen etc. anzeigen. +\n +\nBitte erlaube den Zugriff im nächsten Dialog, damit Benachrichtigungen angezeigt werden können. \ No newline at end of file From 491b5cb537da3fde90fbbe735fc6b5f0c9029f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 16 Oct 2022 08:20:54 +0000 Subject: [PATCH 0112/1068] Translated using Weblate (Estonian) Currently translated at 98.6% (2437 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- .../ui-strings/src/main/res/values-et/strings.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index d0f3b540e2..ecb7b9757c 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -1158,10 +1158,10 @@ Tõuketeavituste reeglid Tõuketeavituste reegleid pole kirjeldatud Tõuketeavituste võrguväravaid pole registreeritud - app_id: - push_key: - app_display_name: - session_name: + Rakenduse ID: + Tõuketeenuse võti: + Rakenduse kuvatav nimi: + Sessiooni nimi: URL: Vorming: Heli ja video @@ -2733,4 +2733,6 @@ Ava arendaja töövahendite vaade Ära iialgi saada selles jututoas krüptitud sõnumeid verifitseerimata sessioonidesse. Selge lugu - + Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim) + Võta kasutusele vormindatud teksti pruukiv tekstitoimeti + \ No newline at end of file From f51b20481de61d06aa506ff80f9d61fee6ca3386 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Fri, 14 Oct 2022 20:12:02 +0000 Subject: [PATCH 0113/1068] Translated using Weblate (Persian) Currently translated at 98.7% (2438 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- .../src/main/res/values-fa/strings.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 47cade0bf8..dbf3658887 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -1492,10 +1492,10 @@ ثبت ژتون فرمت: آدرس: - نام نشست: - نام برنامه: - کلید push: - شناسه برنامه: + نام نمایشی نشست: + نام نمایشی کاره: + کلید ارسال: + شناسهٔ کاره: هیچ push gateway‌ای ثبت نشده است هیچ قانونی برای push تعریف نشده است شما در حال مشاهده این اتاق هستید! @@ -2720,4 +2720,10 @@ گشودن صفحهٔ ابزارهای توسعه‌دهنده به کار انداختن پیام‌های مستقیم تعویقی گرفتم - + آغاز یک پخش همگانی صوتی + (╯°□°)╯︵ ┻━┻ را به ابتدای پیام متنی خام می‌افزاید + پخش همگانی صدا + اعطای دسترسی + ویرایشگر متن غنی را بیازمایید (حالت متن خام به زودی) + به کار انداختن ویرایشگر متن غنی + \ No newline at end of file From 522dd2744e40d8ddbfc49589b61fcab98409e951 Mon Sep 17 00:00:00 2001 From: Glandos Date: Sat, 15 Oct 2022 19:53:52 +0000 Subject: [PATCH 0114/1068] Translated using Weblate (French) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- .../src/main/res/values-fr/strings.xml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index 860840486e..b77173519d 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -869,10 +869,10 @@ Règles de notification Aucune règle de notification définie Aucune passerelle de notification enregistrée - app_id : - push_key : - app_display_name : - session_name : + App ID : + Clé Push : + Nom d’affichage de l’application : + Nom d’affichage de la session : URL : Format : Voix et vidéo @@ -2742,4 +2742,34 @@ ⚠ Il y a des appareils non vérifiés dans ce salon, ils ne pourront pas déchiffrer vos messages envoyés. Ne jamais envoyer de messages chiffrés aux sessions non vérifiées dans ce salon. Compris - + Souligner le texte + Barrer le texte + Mettre en italique + Mettre en gras + Enregistre le nom du client, sa version, et son URL pour retrouvez vos sessions plus facilement dans le gestionnaire de sessions. + Activer l’enregistrement des informations du client + Ayez une meilleur visibilité et plus de contrôle sur toutes vos sessions. + Activer le nouveau gestionnaire de session + Système d’exploitation + Modèle + Navigateur + URL + Version + Nom + Application + Recevoir les notifications push sur cette session. + Notifications push + Vérifiez votre session actuelle pour découvrir le statut de vérification de cette session. + Status de vérification inconnu + Activer : + Identifiant de session : + Quelque chose s’est mal passé. Vérifiez votre connexion réseau et réessayez. + Accorder la permission + ${app_name} a besoin d’une permission pour afficher les notifications. +\nVeuillez accorder la permission. + ${app_name} a besoin de la permission pour afficher les notifications. Les notifications peuvent afficher vos messages, vos invitations, etc. +\n +\nVeuillez autoriser l’accès sur la prochaine fenêtre pour pouvoir voir des notifications. + Essayer l’éditeur de texte formaté (le mode texte brut arrive bientôt) + Activer l’éditeur de texte formaté + \ No newline at end of file From 209ad450c19016932749cfa7f00fc0e4a26fda67 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sun, 16 Oct 2022 15:25:57 +0000 Subject: [PATCH 0115/1068] Translated using Weblate (Hungarian) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- .../src/main/res/values-hu/strings.xml | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index d2e9568053..57510e55a9 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -828,10 +828,10 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze „Push” szabályok „Push” szabályok nincsenek „Push” átjárók nincsenek regisztrálva - app_id: - push_key: - app_display_name: - session_name: + Alk azon: + Push kulcs: + Alk. képernyő név: + Munkamenet képernyő név: Url: Formátum: Hang & Videó @@ -897,7 +897,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Amint hozzáadtál egy telefonszámot megjelenik a felderítési beállítási lehetőség. Az azonosítási szerverről való lecsatlakozással nem leszel mások által megtalálható és másokat sem tudsz meghívni e-mail címmel vagy telefonszámmal. Felderíthető telefonszámok - Megerősítő levelet küldtünk ide: %s, ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra + E-mailt küldtünk ide: %s, ellenőrizd és kattints a megerősítő hivatkozásra Add meg az azonosítási szerver URL-jét Az azonosítási szerverhez nem lehet csatlakozni Kérlek add meg az azonosítási szerver url-jét @@ -1391,7 +1391,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Felhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra. Üzenet eltávolítva Helykitöltő mutatása a törölt szövegek helyett - Megerősítő levelet küldtünk ide: %s, először ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra + E-mailt küldtünk ide: %s, először ellenőrizd és kattints a megerősítő hivatkozásra MÉDIA FÁJLOK %1$s itt: %2$s @@ -2709,4 +2709,67 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Késleltetett közvetlen üzenetek engedélyezése Egyszerűsített Element opcionálisan lapokkal Új kinézet engedélyezése - + Más felhasználók akikkel közvetlenül vagy szobában beszélgetsz látják a teljes listát a munkameneteidről. +\n +\nEzzel ők biztosak lehetnek abban, hogy ténylegesen veled beszélgetnek. Ez azt is jelenti, hogy látják a munkamenet nevét amit itt megadsz. + Ellenőrzött munkamenetbe a neveddel és jelszavaddal léptek be és ellenőrizve lett vagy a biztonsági jelmondattal vagy másik munkamenetből. +\n +\nEz azt jelenti, hogy tartalmazzák a titkosítási kulcsokat az régi üzenetekhez, és biztosítja a többieket a kommunikációban, hogy ezt a munkamenetet tényleg te használod. + Aláhúzott + Áthúzott + Dőlt + Félkövér + Kliens neve, verziója és url felvétele a munkamenet könnyebb azonosításához a munkamenet kezelőben. + Kliens információ felvételének engedélyezése + Jobb áttekintés és felügyelet a munkamenetek felett. + Új munkamenet kezelő engedélyezése + Munkamenet átnevezése + Hitelesített munkamenetek + Az ellenőrizetlen munkamenetek azok amikre a felhasználói neveddel és jelszavaddal léptek be de nem lett ellenőrizve. +\n +\nMindenképpen győződj meg arról, hogy felismered ezeket a munkameneteket mert lehet, hogy illetéktelenül használják a fiókodat. + Ellenőrizetlen munkamenetek + Az inaktív munkamenetek azok amiket egy ideje nem használtál, de továbbra is megkapják a titkosítási kulcsokat. +\n +\nA nem aktív munkamenetek törlésével növelhető a biztonság és a sebesség valamint könnyebb lesz felismerni a gyanús munkameneteket. + Nem aktív munkamenetek + Fontos, hogy a munkamenet neve a kommunikációban résztvevők számára látható. + Az egyedi munkamenet név segíthet az eszköz könnyebb felismerésében. + Munkamenet neve + Munkamenet átnevezése + Operációs rendszer + Modell + Böngésző + URL + Verzió + Név + Alkalmazás + Push értesítések fogadása ebben a munkamenetben. + Push értesítések + Kijelentkezés ebből a munkamenetből + Ellenőrizetlen · A jelenlegi munkameneted + Ellenőrizd a jelenlegi munkamenetedet, hogy ismert állapotba kerüljön. + Ismeretlen ellenőrzési státusz + Hang közvetítés indítása + A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni. + Utasítja a billentyűzetet, hogy ne mentsen személyre szabott adatokat, mint előzmények vagy szótár abból amit a beszélgetésekben írsz. Vedd figyelembe, hogy nem minden billentyűzet veszi ezt figyelembe. + Inkognitó billentyűzet + (╯°□°)╯︵ ┻━┻ -t tesz a szöveg elejére + Hang közvetítés + Engedélyezve: + Munkamenet azon.: + Valami nem sikerült. Kérlek ellenőrizd a hálózati kapcsolatot és próbáld újra. + A fejlesztői eszközök képernyő megnyitása + 🔒 Bekapcsoltad a Biztonsági beállításoknál, hogy csak ellenőrzött munkamenetek számára legyen titkosítva az üzenet bármely szobában. + ⚠ Ellenőrizetlen eszközök vannak a szobában, ezek nem fogják tudni visszafejteni az általad küldött üzeneteket. + Sose küldj titkosított üzenetet ellenőrizetlen munkamenetbe ebből a munkamenetből ebben a szobában. + Engedély megadása + ${app_name} alkalmazásnak értesítések megjelenítéséhez engedélyre van szüksége. +\nKérjük, adj rá engedélyt. + ${app_name} alkalmazásnak szüksége van engedélyre az értesítések megjelenítéséhez. Az értesítés megjelenítheti az üzenetet, meghívót, stb. +\n +\nA következő felugró ablakban adj rá engedélyt, hogy az értesítések megjelenhessenek. + Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód) + Vizuális szerkesztő engedélyezése + Értem + \ No newline at end of file From 0551cf9945d05363823d28c78bce4b4d642f01b5 Mon Sep 17 00:00:00 2001 From: Linerly Date: Sat, 15 Oct 2022 01:59:07 +0000 Subject: [PATCH 0116/1068] Translated using Weblate (Indonesian) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- .../src/main/res/values-in/strings.xml | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 7a389a3fca..137805f8b0 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -1240,10 +1240,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Suara & Video Format: Url: - session_name: - app_display_name: - push_key: - app_id: + Nama Tampilan Sesi: + Nama Tampilan Aplikasi: + Kunci Dorongan: + ID Aplikasi: Tidak ada gateway dorong terdaftar Tidak ada aturan push yang ditentukan Aturan Push @@ -1632,7 +1632,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Lanjut Email (opsional) Email - Atur sebuah alamat email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email secara opsional. + Atur sebuah alamat email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email ini secara opsional. Atur alamat email Kata sandi Anda belum diubah. \n @@ -2690,4 +2690,34 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. ⚠ Ada perangkat yang belum diverifikasi di ruangan ini, mereka tidak akan mendekripsikan pesan yang Anda kirim. Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi di ruangan ini. Saya mengerti - + Terapkan format garis bawah + Terapkan format coret + Terapkan format miring + Terapkan format tebal + Rekam nama klien, versi, dan URL untuk lebih mudah mengenal sesi di pengelola sesi. + Aktifkan perekaman info klien + Miliki keterlihatan dan kendali yang lebih baik pada semua sesi Anda. + Aktifkan pengelola sesi baru + Sistem operasi + Model + Peramban + URL + Versi + Nama + Aplikasi + Terima notifikasi dorongan di sesi ini. + Notifikasi dorongan + Verifikasi sesi Anda saat ini untuk menampilkan status verifikasi sesi ini. + Status verifikasi tidak diketahui + Diaktifkan: + ID Sesi: + Ada sesuatu yang salah. Mohon periksa koneksi jaringan Anda dan coba lagi. + Berikan Izin + ${app_name} membutuhkan izin untuk menampilkan notifikasi. +\nMohon berikan izin itu. + ${app_name} membutuhkan izin untuk menampilkan notifikasi. Notifikasi dapat menampilkan pesan Anda, undangan Anda, dll. +\n +\nMohon perbolehkan akses di munculan berikutnya untuk dapat melihat notifikasi. + Coba editor teks kaya (mode teks biasa akan datang) + Aktifkan editor teks kaya + \ No newline at end of file From 5a9032b45cba1178405cd63b821db5ba8a8bc1b5 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 17 Oct 2022 10:43:33 +0000 Subject: [PATCH 0117/1068] Translated using Weblate (Italian) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- .../src/main/res/values-it/strings.xml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index f65aca5bb7..4bd4a5401c 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -889,10 +889,10 @@ Regole di push Nessuna regola di push definita Nessun gateway di push registrato - id_app: - chiave_push: - nome_visualizzato_app: - nome_sessione: + ID app: + Chiave push: + Nome mostrato app: + Nome mostrato sessione: Url: Formato: Audio e Video @@ -2733,4 +2733,34 @@ ⚠ Ci sono dispositivi non verificati in questa stanza, non potranno decifrare i messaggi che invii. Non inviare mai messaggi cifrati a sessioni non verificate in questa stanza. Capito - + Applica formato sottolineato + Applica formato sbarrato + Applica formato corsivo + Applica formato grassetto + Registra il nome, la versione e l\'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni. + Attiva registrazione info client + Maggiore visibilità e controllo su tutte le tue sessioni. + Attiva il nuovo gestore di sessioni + Sistema operativo + Modello + Browser + URL + Versione + Nome + Applicazione + Ricevi notifiche push in questa sessione. + Notifiche push + Verifica l\'attuale sessione per rivelare lo stato di verifica di questa sessione. + Stato di verifica sconosciuto + Attivato: + ID sessione: + Qualcosa è andato storto. Controlla la tua connessione di rete e riprova. + Concedi l\'autorizzazione + ${app_name} chiede l\'autorizzazione per mostrare notifiche. +\nConcedi l\'autorizzazione. + ${app_name} chiede l\'autorizzazione per mostrare notifiche. Le notifiche possono mostrare i messaggi, gli inviti, ecc. +\n +\nConsenti l\'accesso nelle prossime schermate per potere vedere la notifica. + Prova l\'editor in rich text (il testo semplice è in arrivo) + Attiva editor in rich text + \ No newline at end of file From b0470dce6a5aae8deb745729fabcb675514931b7 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Sat, 15 Oct 2022 01:55:43 +0000 Subject: [PATCH 0118/1068] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- .../src/main/res/values-pt-rBR/strings.xml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index e0d6e6aa86..7a709757c9 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -1007,10 +1007,10 @@ Regras de Push Nenhuma regra de push definida Nenhum gateway de push registrado - app_id: - push_key: - app_display_name: - session_name: + ID do App: + Chave Push: + Nome de Exibição do App: + Nome de Exibição da Sessão: Url: Formato: Voz & Vídeo @@ -2742,4 +2742,34 @@ ⚠ Existem dispositivos não-verificados nesta sala, eles não vão ser capazes de decriptar mensagens que você enviar. Nunca enviar mensagens encriptadas a sessões não-verificadas nesta sala. Entendido - + Aplicar formato tachar + Aplicar formato sublinhar + Aplicar formato itálico + Aplicar formato negrito + Gravar o nome de cliente, versão, e url para reconhecer sessões mais facilmente em gerenciador de sessão. + Habilitar gravação de info de cliente + Tenha visibilidade e controle maiores sobre todas suas sessões. + Habilitar novo gerenciador de sessão + Sistema operativo + Modelo + Browser + URL + Versão + Nome + Aplicativo + Receber notificações push nesta sessão. + Notificações push + Verifique sua sessão atual para revelar o status de verificação desta sessão. + Status de verificação desconhecido + Habilitado: + ID da Sessão: + Algo deu errado. Por favor cheque sua conexão de rede e tente de novo. + Conceder Permissão + ${app_name} precisa de permissão para mostrar notificações. +\nPor favor conceda a permissão. + ${app_name} precisa de permissão para exibir notificações. Notificações podem exibir suas mensagens, seus convites, etc. +\n +\nPor favor permita acesso nos próximos pop-ups para ser capaz de visualizar notificação. + Experimente o editor de texto rico (modo de texto puro vindo em breve) + Habilitar editor de texto rico + \ No newline at end of file From 440e3be7394a0387abdb03697f026758ad5948d0 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Mon, 17 Oct 2022 10:19:05 +0000 Subject: [PATCH 0119/1068] Translated using Weblate (Russian) Currently translated at 96.7% (2389 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- .../src/main/res/values-ru/strings.xml | 96 +++++++++++-------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 8d19e8d9d0..1953e46698 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -398,8 +398,8 @@ Прикрепить комнаты с отключенными уведомлениями Прикрепить комнаты с непрочитанными сообщениями ID - Публичное имя - Обновить публичное имя + Публичное название + Обновить публичное название Недавно %1$s @ %2$s Аутентификация @@ -431,7 +431,7 @@ Установить как основной адрес Сбросить основной адрес Ошибка дешифровки - Публичное имя + Публичное название ID сессии Ключ сессии Экспорт E2E ключей комнаты @@ -727,21 +727,21 @@ Беззвучный Пожалуйста, введите мнемоническую фразу Парольная фраза слишком простая - Пожалуйста, удалите мнемоническую фразу, если хотите, чтобы ${app_name} сгенерировал ключ восстановления. + Пожалуйста, удалите мнемоническую фразу, если хотите, чтобы ${app_name} сгенерировал бумажный ключ. Никогда не теряйте зашифрованных сообщений Сообщения в зашифрованных комнатах защищены сквозным шифрованием. Ключи для прочтения этих сообщений есть только у вас и получателя(ей). \n \nНадёжно сохраните резервную копию ключей, чтобы не потерять их. Установите парольную фразу - Сохраните ключ восстановления + Сохранить бумажный ключ Готово Сохранить как файл Пожалуйста, сделайте копию - Поделиться ключом восстановления с… - Ключ для восстановления + Поделиться бумажным ключом с… + Бумажный ключ Непредвиденная ошибка Уверены? - Удалить резервную копию ключей шифрования с сервера? Вы больше не сможете использовать ключ восстановления для чтения истории зашифрованных сообщений. + Удалить резервную копию ключей шифрования с сервера\? Вы больше не сможете использовать бумажный ключ для чтения истории зашифрованных сообщений. Удалить резервную копию Удаление резервной копии… Чтобы использовать резервную копию ключа в этой сессии, восстановите его с помощью своей парольной фразы или ключа восстановления. @@ -755,17 +755,17 @@ Резервное копирование ключей успешно настроено для этой сессии. Удалить резервную копию Восстановить из резервной копии - Пожалуйста, введите ключ восстановления + Пожалуйста, введите бумажный ключ Разблокировать историю Восстановление резервной копии: - Введите ключ восстановления - Используйте ключ восстановления для разблокировки истории зашифрованных сообщений - Если вы не знаете вашу парольную фразу для восстановления, вы можете %s. - используйте ключ восстановления + Введите бумажный ключ + Используйте бумажный ключ для разблокировки зашифрованных сообщений + Если забыли свою мнемоническую фразу, вы можете %s. + используйте бумажный ключ Вы можете потерять доступ к сообщениям, если выйдете из системы или потеряете это устройство. Получение версии резервной копии… - Используйте парольную фразу для разблокировки истории зашифрованных сообщений - Потеряли ключ восстановления? В настройках вы можете создать новый. + Используйте мнемоническую фразу для разблокировки зашифрованных сообщений + Потеряли бумажный ключ\? В настройках вы можете создать новый. Резервная копия восстановлена %s ! Резервная копия имеет недействительную подпись из неподтвержденной сессии %s Не удалось получить последнюю версию ключей восстановления (%s). @@ -780,9 +780,9 @@ Восстановлены резервные копии с %d ключами. Восстановлены резервные копии с %d ключами. - Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ. - Невозможно расшифровать резервную копию с помощью этого пароля: убедитесь, что вы ввели правильный пароль. - Генерация ключей восстановления с использованием парольной фразы может занять несколько секунд. + Невозможно расшифровать резервную копию с помощью этого бумажного ключа: пожалуйста, убедитесь, что вы ввели правильный бумажный ключ. + Невозможно расшифровать резервную копию с помощью этой мнемонической фразы: пожалуйста, убедитесь, что вы ввели правильную мнемоническую фразу. + Генерация бумажного ключа с использованием мнемонической фразы может занять несколько секунд. [%1$s] \nЭта ошибка вне контроля ${app_name}. На телефоне нет учетной записи Google. Пожалуйста, добавьте аккаунт Google. [%1$s] @@ -816,7 +816,7 @@ Никогда не теряйте зашифрованные сообщения Поделиться Я сделал(а) копию - Храните ключ восстановления в надежном месте, например, в диспетчере паролей (или в сейфе) + Храните бумажный ключ в очень надёжном месте, например, в менеджере паролей (или в сейфе) Защитите резервную копию мнемонической фразой. Восстановление зашифрованных сообщений Начать использовать резервное копирование ключей @@ -825,16 +825,16 @@ Новые ключи зашифрованных сообщений Ваши ключи копируются. (Дополнительно) Настройка с ключом восстановления - Или защитите резервную копию с помощью ключа восстановления, сохранив его в безопасном месте. + Или защитите резервную копию бумажным ключом, сохранив его в надёжном месте. Безопасная резервная копия ключей должна быть активирована на всех ваших сессиях, чтобы не потерять доступ к зашифрованным сообщениям. - Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её парольной фразой. + Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её мнемонической фразой. \n -\nДля максимальной безопасности парольная фраза должна отличаться от пароля вашей учётной записи. +\nДля максимальной безопасности мнемоническая фраза должна отличаться от пароля вашей учётной записи. Ключ восстановления — это страховка, вы можете использовать его для восстановления доступа к вашим зашифрованным сообщениям, если забудете вашу парольную фразу. \nХраните ключ восстановления в надёжном месте, например, в диспетчере паролей (или в сейфе) Импортирование ключей… Скачивание ключей… - Вычисление ключа восстановления… + Вычисление бумажного ключа… Игнорировать Отметить как прочитанное Войти с помощью единого входа @@ -929,10 +929,10 @@ Предпочтения Безопасность Правила push-уведомлений - app_id: + ID приложения: push_key: - app_display_name: - session_name: + Отображаемое название приложения: + Отображаемое название сессии: Url: Формат: Голос и видео @@ -1219,10 +1219,10 @@ Ещё QR-код Соединение с сервером потеряно - Используйте пароль восстановления или ключ + Используйте мнемоническую фразу или бумажный ключ Разблокировать историю зашифрованных сообщений Проверка была отменена. Вы можете начать проверку снова. - Мнемоническая фраза для восстановления + Мнемоническая фраза Введите %s, чтобы продолжить. Не переиспользуйте пароль учётной записи. Это может занять несколько секунд, пожалуйста, наберитесь терпения. @@ -1314,7 +1314,7 @@ Сброс безопасного резервного копирования Настроить на этом устройстве Защитите себя от потери доступа к зашифрованным сообщениям и данным, создав резервные копии ключей шифрования на вашем сервере. - Создайте новый ключ безопасности или задайте новую секретную фразу для существующей резервной копии. + Создайте новый бумажный ключ или задайте новую мнемоническую фразу для существующей резервной копии. Это заменит ваш текущий ключ или фразу. Интеграции отключены Включите «Управление интеграциями» в настройках, чтобы сделать это. @@ -1326,7 +1326,7 @@ Ключи успешно экспортированы ОБЗОР Активные виджеты - Ключ восстановления был сохранён. + Бумажный ключ сохранён. Безопасное резервное копирование Защита от потери доступа к зашифрованным сообщениям и данным Настроить безопасное резервное копирование @@ -1553,12 +1553,12 @@ Эта учётная запись была деактивирована. Введите %s, чтобы продолжить Использовать файл - Это недействительный ключ восстановления - Пожалуйста, введите ключ восстановления + Этот бумажный ключ недействителен + Пожалуйста, введите бумажный ключ Проверка ключа резервного копирования Проверка ключа резервного копирования (%s) Получение кривой ключа - Генерация ключа SSSS из ключа восстановления + Генерация ключа SSSS из бумажного ключа Сохранение резервной копии ключа в SSSS используйте ваш ключ восстановления ключа резервной копии Ключ восстановления ключа резервной копии @@ -1571,7 +1571,7 @@ \n${app_name} для Android или другой клиент Matrix поддерживающий перекрестную подпись Принудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату - Чтобы продолжить, используйте ваш %1$s или используйте ваш %2$s. + Чтобы продолжить, используйте %1$s или %2$s. Используйте ключ восстановления Выберите ключ восстановления или введите его вручную, введя или вставив из буфера обмена Не удалось получить доступ к защищенному хранилищу данных @@ -1624,13 +1624,13 @@ Настроить Используйте ключ безопасности Создайте ключ безопасности для хранения в надежном месте, например в менеджере паролей или сейфе. - Использовать секретную фразу + Использовать мнемоническую фразу Введите секретную фразу, известную только вам, и создайте ключ для резервного копирования. Сохраните свой ключ безопасности - Храните ключ безопасности в надежном месте, например в менеджере паролей или сейфе. + Храните бумажный ключ в надёжном месте, например, в менеджере паролей или в сейфе. Задайте секретную фразу Введите секретную фразу, известную только вам, для защиты данных на вашем сервере. - Секретная фраза + Мнемоническая фраза Для подтверждения введите вашу секретную фразу ещё раз. Название комнаты Тема @@ -1646,7 +1646,7 @@ Мы рады сообщить, что сменили имя! Ваше приложение обновлено, и вы вошли в свою учетную запись. ПОНЯТНО УЗНАТЬ БОЛЬШЕ - Сохранить ключ восстановления в + Сохранить бумажный ключ в Получаем ваши контакты… Ваша контактная книга пуста Книга контактов @@ -1706,7 +1706,7 @@ Удалить %s\? Убедитесь, что вы перешли по ссылке в электронном письме, которое мы вам отправили. Электронная почта и номера телефонов - Управляйте электронной почтой и номерами телефонов, привязанными к вашей учетной записи Matrix + Управляйте адресами электронной почты и номерами телефонов, привязанными к вашей учётной записи Matrix Код Используйте международный формат (номер телефона должен начинаться с \'+\') Подтвердите свою личность, проверив этот логин, предоставив ему доступ к зашифрованным сообщениям. @@ -2633,7 +2633,7 @@ Где хранятся ваши переписки Где будут храниться ваши переписки Должно быть 8 или более символов - Не удалось подтвердить это устройство + Не удалось подтвердить эту сессию Невозможно открыть эту ссылку: сообщества были заменены пространствами Имя пользователя / Почта / Телефон Следуйте инструкциям, отправленным на %s @@ -2758,11 +2758,23 @@ Понятно 🔒 В настройках безопасности вы включили шифрование только для заверенных сессий во всех комнатах. Не отправлять зашифрованные сообщения незаверенным сессиям в этой комнате. - Неактивные сессии — это сессии, которыми вы не пользовались определенное время, но они продолжают получать ключи шифрования. + Неактивные сессии — это сессии, которыми вы не пользовались определённое время, но они продолжают получать ключи шифрования. \n \nУдаление неактивных сессий повышает безопасность и производительность, а также облегчает выявление подозрительных новых сессий. Переименование сессий Другие пользователи в личных сообщениях и комнатах, к которым вы присоединились, могут просматривать весь список ваших сессий. \n \nЭто даёт им уверенность в том, что они действительно общаются с вами, но это также означает, что они могут видеть название сессии, которое вы ввели здесь. - + Визуальный редактор текста + ID сессии: + Уведомления + Получать push-уведомления в этой сессии. + URL-адрес + Приложение + Название + Версия + Веб-браузер + Модель + Операционная система + Новый менеджер сессий + \ No newline at end of file From a1a9d8f150fb1917477a92fc32c7e82db3ba1dad Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 15 Oct 2022 19:12:22 +0000 Subject: [PATCH 0120/1068] Translated using Weblate (Slovak) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- .../src/main/res/values-sk/strings.xml | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index d4c3f8c40c..ac2c4bea4a 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -1700,7 +1700,7 @@ Prosím, použite medzinárodný formát. Nastavte si telefónne číslo, aby ste voliteľne umožnili ľuďom, ktorých poznáte, aby vás objavili. Toto nevyzerá ako platná e-mailová adresa - Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť známym, aby vás objavili podľa vášho e-mailu. + Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť svojim známym, aby vás objavili podľa tohto e-mailu. Nastaviť e-mailovú adresu Späť na prihlásenie Vaše heslo bolo obnovené. @@ -2796,4 +2796,34 @@ ⚠ V tejto miestnosti sa nachádzajú neoverené zariadenia, ktoré nebudú schopné dešifrovať odoslané správy. Nikdy neposielať šifrované správy do neoverených relácií v tejto miestnosti. Rozumiem - + Použiť formát podčiarknutia + Použiť formát prečiarknutia + Použiť formát kurzívou + Použiť tučný formát + Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií. + Povoliť zaznamenanie informácií o klientovi + Majte lepší prehľad a kontrolu nad všetkými reláciami. + Použiť nového správcu relácií + Operačný systém + Model + Prehliadač + URL + Verzia + Názov + Aplikácia + Prijímať push oznámenia v tejto relácii. + Push oznámenia + Overením aktuálnej relácie zistíte stav overenia tejto relácie. + Neznámy stav overenia + Zapnuté: + ID relácie: + Niečo sa pokazilo. Skontrolujte, prosím, svoje sieťové pripojenie a skúste to znova. + Udeliť oprávnenie + ${app_name} potrebuje povolenie na zobrazovanie oznámení. +\nProsím, udeľte toto povolenie. + ${app_name} potrebuje povolenie na zobrazovanie oznámení. Oznámenia môžu zobrazovať vaše správy, pozvánky atď. +\n +\nPovoľte prístup na ďalších vyskakovacích oknách, aby ste mohli zobrazovať oznámenia. + Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro) + Povoliť rozšírený textový editor + \ No newline at end of file From b2e2cbaf47712ab20a746a7301f3b4bca573e9b8 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 15 Oct 2022 20:07:31 +0000 Subject: [PATCH 0121/1068] Translated using Weblate (Swedish) Currently translated at 98.2% (2426 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- .../src/main/res/values-sv/strings.xml | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index bf083a1117..1f38d2069c 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -615,7 +615,7 @@ Jag har verifierat min e-postadress Du har blivit utloggad ur alla sessioner och kommer inte längre motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet. Sätt e-postadress - Sätt en e-postadress för att kunna återförva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med din e-postadress. + Sätt en e-postadress för att kunna återförvärva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med den här e-postadressen. E-post E-post (valfritt) Sätt ett telefonnummer som valfritt kan användas för att vara upptäckbar av folk som känner dig. @@ -1312,10 +1312,10 @@ Ett fel inträffade vid hämtning av nyckelsäkerhetskopia Du tittar redan på det här rummet! Inga registrerade pushgateways - app_id: - push_key: - app_display_name: - session_name: + App-ID: + Pushnyckel: + Appens visningsnamn: + Sessionens visningsnamn: Url: Format: Registrera token @@ -2651,4 +2651,71 @@ Favoriter Olästa Alla - + Pushnotiser + Applikations-, enhets- och aktivitetsinformation. + Sessionsdetaljer + Logga ut ur den här sessionen + Rensa filter + Inga inaktiva sessioner hittade. + Inga overifierade sessioner hittade. + Inga verifierade sessioner hittade. + + Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) du inte använder längre. + Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) du inte använder längre. + + Inaktiv + Verifiera dina sessioner för förbättrad säker meddelandehantering eller logga ut ur de du inte känner igen eller använder längre. + Overifierad + För bäst säkerhet, logga ut från sessioner du inte känner igen eller använder längre. + Verifierad + Filter + + Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) som du inte använder längre. + Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) som du inte använder längre. + + + Inaktiv %1$d dag eller längre + Inaktiv %1$d dagar eller längre + + Inaktiv + Inte redo för säkra meddelanden + Overifierad + Redo för säkra meddelanden + Verifierade + Alla sessioner + Filter + Senast aktiv %1$s + Enhet + Session + Nuvarande session + Inaktiva sessioner + Verifiera eller logga ut ur overifierade sessioner. + Overifierade sessioner + Förbättra din kontosäkerhet genom att följa dessa rekommendationer. + Säkerhetsrekommendationer + + Inaktiv %1$d+ dag (%2$s) + Inaktiv %1$d+ dagar (%2$s) + + Overifierad · Din nuvarande session + Overifierad · Senast aktiv %1$s + Verifierad · Senast aktiv %1$s + Visa alla (%1$d) + Visa detaljer + Verifiera session + Verifiera din nuvarande session för att visa den här sessionens verifieringsstatus. + Verifiera eller logga ut från den här sessionen för bäst säkerhet och pålitlighet. + Verifiera din nuvarande session för förbättrad säker meddelandehantering. + Okänd verifieringsstatus + Aktiverad: + Sessions-ID: + Nåt gick fel. Kolla din nätverksanslutning och pröva igen. + Ge åtkomst + ${app_name} behöver behörighet att visa aviseringar. +\nVänligen ge åtkomst. + ${app_name} behöver behörighet att visa aviseringar. Aviseringar kan visa dina meddelanden, dina inbjudningar, o.s.v. +\n +\nVänligen ge åtkomst på nästa pop-uper för att kunna se aviseringar. + Aktivera rik-text-redigerare + Testa den nya rik-text-redigeraren + \ No newline at end of file From d05ad9425efaec64ae67755823d935008d3cbed6 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 14 Oct 2022 21:12:28 +0000 Subject: [PATCH 0122/1068] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- .../src/main/res/values-uk/strings.xml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 45ee44213c..20b7348bdf 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -1740,10 +1740,10 @@ Відгук Формат: Url: - session_name: - app_display_name: - push_key: - app_id: + Показувана назва сеансу: + Показувана назва застосунку: + Ключ Push: + ID застосунку: Версія Matrix SDK Кімнату створено, але деякі запрошення не надіслано з такої причини: \n @@ -2850,4 +2850,34 @@ ⚠ У цій кімнаті є неперевірені пристрої, вони не зможуть розшифрувати повідомлення, які ви надсилаєте. Ніколи не надсилати зашифровані повідомлення на неперевірені сеанси в цій кімнаті. Зрозуміло - + Застосувати форматування підкресленим + Застосувати форматування перекресленим + Застосувати форматування курсивом + Застосувати форматування жирним + Записуйте назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів. + Увімкнути запис відомостей про клієнт + Отримайте кращу видимість і контроль над усіма вашими сеансами. + Увімкнути новий менеджер сеансів + Операційна система + Модель + Браузер + URL + Версія + Назва + Застосунок + Отримувати push-сповіщення про цей сеанс. + Push-сповіщення + Звірте свій поточний сеанс, щоб побачити стан перевірки цього сеансу. + Невідомий стан перевірки + Увімкнено: + ID сеансу: + Щось пішло не так. Будь ласка, перевірте мережеве з\'єднання та спробуйте ще раз. + Надати дозвіл + ${app_name} потребує дозволу на показ сповіщень. +\nНадайте дозвіл. + Для показу сповіщень ${app_name} потрібен дозвіл. Сповіщення можуть показувати ваші повідомлення, запрошення тощо. +\n +\nДозвольте доступ до наступних спливних вікон, щоб мати змогу переглядати сповіщення. + Спробуйте розширений текстовий редактор (незабаром з\'явиться режим звичайного тексту) + Увімкнути розширений текстовий редактор + \ No newline at end of file From fd226b8eab68cd83ba8696638f45b190439042de Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 17 Oct 2022 02:42:41 +0000 Subject: [PATCH 0123/1068] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2470 of 2470 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- .../src/main/res/values-zh-rTW/strings.xml | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index e3cd44adca..b72f9bc5c6 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -873,10 +873,10 @@ 推送規則 未定義通送規則 沒有已註冊的推送閘道 - app_id: - push_key: - app_display_name: - session_name: + App ID: + 推送金鑰: + 應用程式顯示名稱: + 工作階段顯示名稱: Url: 格式: 音訊與視訊 @@ -1092,7 +1092,7 @@ \n \n停止密碼變更流程? 設定電子郵件地址 - 設定電子郵件地址以復原您的帳號。之後您也可以選擇性地讓您認識的人透過您的這個地址找到您。 + 設定電子郵件地址以復原您的帳號。之後您也可以選擇性地讓您認識的人透過此地址找到您。 電子郵件 電子郵件(選擇性) 下一個 @@ -2688,4 +2688,34 @@ ⚠ 此聊天室中有未驗證的裝置,它們將無法解密您傳送的訊息。 切莫向此聊天室中未經驗證的工作階段傳送加密訊息。 知道了 - + 套用底線格式 + 套用刪除線格式 + 套用義式斜體格式 + 套用粗體格式 + 記錄客戶端名稱、版本與 URL,以便在工作階段管理程式中可以更簡單地辨認工作階段。 + 啟用客戶端資訊記錄 + 對所有工作階段有更大的能見度與控制。 + 啟用新的工作階段管理程式 + 作業系統 + 模型 + 瀏覽器 + URL + 版本 + 名稱 + 應用程式 + 接收關於此工作階段的推播通知。 + 推播通知 + 驗證您目前的工作階段以顯示此工作階段的驗證狀態。 + 未知的驗證狀態 + 已啟用: + 工作階段 ID: + 發生了一些問題。請檢查您的網路連線並再試一次。 + 授予權限 + ${app_name} 需要權限以顯示通知。 +\n請授予權限。 + ${app_name} 需要權限才能顯示通知。通知可以顯示您的訊息、您的邀請等等。 +\n +\n請在下一個彈出式視窗允許存取以檢視通知。 + 試用格式化文字編輯器(純文字模式即將推出) + 啟用格式化文字編輯器 + \ No newline at end of file From d3e61a23a9a23ea75fe57f5b4c01cf25ea642558 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 17:29:45 +0100 Subject: [PATCH 0124/1068] Fix generator --- .../api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt index ab58179f5a..75f0024fda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.rendezvous.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -@JsonClass(generateAdapter = true) +@JsonClass(generateAdapter = false) enum class SecureRendezvousChannelAlgorithm(val value: String) { @Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") From 41dbdbcd7bc5c492620612f4e7f3bcd05c89e7e1 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 17:30:54 +0100 Subject: [PATCH 0125/1068] Lint --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index ed2fb6e0f5..2647c5974d 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -37,7 +37,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( @Assisted private val initialState: QrCodeLoginViewState, private val authenticationService: AuthenticationService, private val activeSessionHolder: ActiveSessionHolder, - private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase + private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase, ) : VectorViewModel(initialState) { @AssistedFactory From 8c8190202f9792b708d623ef5944016220edc69c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 17:31:14 +0100 Subject: [PATCH 0126/1068] Better function name --- .../vector/app/features/login/qr/QrCodeLoginStatusFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt index 097c956f2c..6ef261e6d9 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt @@ -66,14 +66,14 @@ class QrCodeLoginStatusFragment : VectorBaseFragment getString(R.string.qr_code_login_header_failed_device_is_not_supported_description) From 8f4d998362d4a89ea0aed78d4abe7c7e2738e9f2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 17:33:43 +0100 Subject: [PATCH 0127/1068] Lint --- .../internal/session/homeserver/GetHomeServerCapabilitiesTask.kt | 1 - 1 file changed, 1 deletion(-) 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 4b56d8e756..2c3cb440b6 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,7 +20,6 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixPatterns.getServerName 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 From 6d17d51fe9e7a5483288f833106f4e572fafcb46 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 17:36:35 +0100 Subject: [PATCH 0128/1068] remove nullability --- .../android/sdk/api/rendezvous/Rendezvous.kt | 4 ++-- .../features/login/qr/QrCodeLoginViewModel.kt | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index 9ad889fca0..e41a72db94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -89,7 +89,7 @@ class Rendezvous( } @Throws(RendezvousError::class) - suspend fun startAfterScanningCode(): String? { + suspend fun startAfterScanningCode(): String { val checksum = channel.connect() Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum") @@ -111,7 +111,7 @@ class Rendezvous( } @Throws(RendezvousError::class) - suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session? { + suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session { Timber.tag(TAG).i("Waiting for login_token") val loginToken = receive() diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 2647c5974d..4134dc8ab2 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -100,21 +100,19 @@ class QrCodeLoginViewModel @AssistedInject constructor( try { val confirmationCode = rendezvous.startAfterScanningCode() Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode") - confirmationCode?.let { - onConnectionEstablished(it) - val session = rendezvous.waitForLoginOnNewDevice(authenticationService) - onSigningIn() - session?.let { - activeSessionHolder.setActiveSession(session) - authenticationService.reset() - configureAndStartSessionUseCase.execute(session) + onConnectionEstablished(confirmationCode) - rendezvous.completeVerificationOnNewDevice(session) + val session = rendezvous.waitForLoginOnNewDevice(authenticationService) + onSigningIn() - _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) - } - } + activeSessionHolder.setActiveSession(session) + authenticationService.reset() + configureAndStartSessionUseCase.execute(session) + + rendezvous.completeVerificationOnNewDevice(session) + + _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen) } catch (t: Throwable) { Timber.tag(TAG).e(t, "Error occurred during sign in") if (t is RendezvousError) { From a88a172f0fc63d65a7c4a43224335b9d8647e261 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 6 Oct 2022 17:04:27 +0200 Subject: [PATCH 0129/1068] Trigger play/pause/resume/stop actions on VoiceRecorder --- .../room/detail/composer/AudioMessageHelper.kt | 17 +++++++++++++++-- .../app/features/voice/AbstractVoiceRecorder.kt | 4 ++-- .../vector/app/features/voice/VoiceRecorder.kt | 10 ++++++++++ .../vector/app/features/voice/VoiceRecorderL.kt | 11 ++++++++++- .../vector/app/features/voice/VoiceRecorderQ.kt | 11 +++++++++++ .../usecase/PauseVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/ResumeVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/StartVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/StopVoiceBroadcastUseCase.kt | 8 +++++++- 9 files changed, 76 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index a5e899c672..f068330cb1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -114,8 +114,13 @@ class AudioMessageHelper @Inject constructor( * When entering in playback mode actually. */ fun pauseRecording() { - voiceRecorder.stopRecord() - stopRecordingAmplitudes() + voiceRecorder.pauseRecord() + pauseRecordingAmplitudes() + } + + fun resumeRecording() { + voiceRecorder.resumeRecord() + resumeRecordingAmplitudes() } fun deleteRecording() { @@ -221,6 +226,14 @@ class AudioMessageHelper @Inject constructor( } } + private fun pauseRecordingAmplitudes() { + amplitudeTicker?.pause() + } + + private fun resumeRecordingAmplitudes() { + amplitudeTicker?.resume() + } + private fun stopRecordingAmplitudes() { amplitudeTicker?.stop() amplitudeTicker = null diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index 5e27aa5bb2..b28d76f176 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -32,7 +32,7 @@ abstract class AbstractVoiceRecorder( ) : VoiceRecorder { private val outputDirectory: File by lazy { ensureAudioDirectory(context) } - private var mediaRecorder: MediaRecorder? = null + protected var mediaRecorder: MediaRecorder? = null private var outputFile: File? = null abstract fun setOutputFormat(mediaRecorder: MediaRecorder) @@ -79,8 +79,8 @@ abstract class AbstractVoiceRecorder( } override fun stopRecord() { - // Can throw when the record is less than 1 second. mediaRecorder?.let { + // Can throw when the record is less than 1 second. tryOrNull { it.stop() } it.reset() it.release() diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 785fb9b4da..761aad2334 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -34,6 +34,16 @@ interface VoiceRecorder { */ fun startRecord(roomId: String) + /** + * Pause the recording. + */ + fun pauseRecord() + + /** + * Resume the recording. + */ + fun resumeRecord() + /** * Stop the recording. */ diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index d5395cc849..1460f1a88f 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -23,6 +23,7 @@ import android.media.MediaRecorder import android.media.audiofx.AutomaticGainControl import android.media.audiofx.NoiseSuppressor import android.os.Build +import android.widget.Toast import io.element.android.opusencoder.OggOpusEncoder import io.element.android.opusencoder.configuration.SampleRate import kotlinx.coroutines.CoroutineScope @@ -40,7 +41,7 @@ import kotlin.coroutines.CoroutineContext * VoiceRecorder to be used on Android versions < [Build.VERSION_CODES.Q]. It uses libopus to record ogg files. */ class VoiceRecorderL( - context: Context, + private val context: Context, coroutineContext: CoroutineContext, private val codec: OggOpusEncoder, ) : VoiceRecorder { @@ -112,6 +113,14 @@ class VoiceRecorderL( } } + override fun pauseRecord() { + Toast.makeText(context, "Not implemented for this Android version", Toast.LENGTH_SHORT).show() + } + + override fun resumeRecord() { + Toast.makeText(context, "Not implemented for this Android version", Toast.LENGTH_SHORT).show() + } + override fun stopRecord() { val recorder = this.audioRecorder ?: return recordingJob?.cancel() diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt index 5091ddfa3b..1eb850b8f5 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt @@ -20,12 +20,23 @@ import android.content.Context import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi +import org.matrix.android.sdk.api.extensions.tryOrNull /** * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. It uses the native OPUS support on Android 10+. */ @RequiresApi(Build.VERSION_CODES.Q) class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") { + + override fun pauseRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.pause() } + } + + override fun resumeRecord() { + mediaRecorder?.resume() + } + override fun setOutputFormat(mediaRecorder: MediaRecorder) { // We can directly use OGG here mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 8f61284423..6c779b7a50 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class PauseVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -60,6 +62,10 @@ class PauseVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO pause recording audio files + pauseRecording() + } + + private fun pauseRecording() { + audioMessageHelper.pauseRecording() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index d0d82b42c3..fbbbc32612 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class ResumeVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -65,6 +67,10 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO resume recording audio files + resumeRecording() + } + + private fun resumeRecording() { + audioMessageHelper.resumeRecording() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 0b8328cd4b..03dbbed5e4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -30,6 +31,7 @@ import javax.inject.Inject class StartVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -62,6 +64,10 @@ class StartVoiceBroadcastUseCase @Inject constructor( ).toContent() ) - // TODO start recording audio files + startRecording(room) + } + + private fun startRecording(room: Room) { + audioMessageHelper.startRecording(room.roomId) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 8b22193770..ed868fced8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class StopVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -61,6 +63,10 @@ class StopVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO stop recording audio files + stopRecording() + } + + private fun stopRecording() { + audioMessageHelper.stopRecording() } } From dbc61971df6f5bab1e44f5d9af7dbd4a30fe3cbd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 17:32:36 +0200 Subject: [PATCH 0130/1068] Reduce duplicated code --- .../room/send/LocalEchoEventFactory.kt | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 4d5e574592..afff51a4a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -403,14 +403,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -447,14 +440,7 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailInfo = thumbnailInfo ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -479,14 +465,7 @@ internal class LocalEchoEventFactory @Inject constructor( waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -500,14 +479,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -629,6 +601,14 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } + private fun generateThreadRelationContent(rootThreadEventId: String) = + RelationDefaultContent( + type = RelationType.THREAD, + eventId = rootThreadEventId, + isFallingBack = true, + inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)) + ) + /** * Generates the appropriate relatesTo object for a reply event. * It can either be a regular reply or a reply within a thread From 33a021c8edd491de5ff769de762a0983f4f35b1f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 18:11:38 +0200 Subject: [PATCH 0131/1068] Add sdk entry to attach reference to the outgoing events --- .../sdk/api/session/room/send/SendService.kt | 5 ++- .../session/room/send/DefaultSendService.kt | 7 ++-- .../room/send/LocalEchoEventFactory.kt | 36 +++++++++++-------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index de9bcfbf0d..7bd6337548 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.PollType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -81,13 +82,15 @@ interface SendService { * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread + * @param relatesTo add a relation content to the media event * @return a [Cancelable] */ fun sendMedia( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, + relatesTo: RelationDefaultContent? = null ): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index a3f2825a0c..aa305e6067 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -280,7 +281,8 @@ internal class DefaultSendService @AssistedInject constructor( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Cancelable { // Ensure that the event will not be send in a thread if we are a different flow. // Like sending files to multiple rooms @@ -295,7 +297,8 @@ internal class DefaultSendService @AssistedInject constructor( localEchoEventFactory.createMediaEvent( roomId = it, attachment = attachment, - rootThreadEventId = rootThreadId + rootThreadEventId = rootThreadId, + relatesTo, ).also { event -> createLocalEcho(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index afff51a4a5..0fb2c9506a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -347,14 +347,21 @@ internal class LocalEchoEventFactory @Inject constructor( fun createMediaEvent( roomId: String, attachment: ContentAttachmentData, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo) + ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo) + ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent( + roomId, + attachment, + isVoiceMessage = true, + rootThreadEventId = rootThreadEventId, + relatesTo + ) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo) } } @@ -378,7 +385,7 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { var width = attachment.width var height = attachment.height @@ -403,12 +410,12 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -440,7 +447,7 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailInfo = thumbnailInfo ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -449,7 +456,8 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, attachment: ContentAttachmentData, isVoiceMessage: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Event { val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -465,12 +473,12 @@ internal class LocalEchoEventFactory @Inject constructor( waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -479,7 +487,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } From e775404e35822ab5e9e9307d589762138ff9dac6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:15:06 +0200 Subject: [PATCH 0132/1068] Improve VoiceRecorder abstraction --- .../detail/composer/AudioMessageHelper.kt | 4 +- .../composer/MessageComposerViewModel.kt | 2 +- .../features/voice/AbstractVoiceRecorder.kt | 75 ++--------- .../features/voice/AbstractVoiceRecorderQ.kt | 124 ++++++++++++++++++ .../app/features/voice/VoiceRecorder.kt | 14 +- .../app/features/voice/VoiceRecorderL.kt | 33 +---- .../app/features/voice/VoiceRecorderQ.kt | 23 +--- 7 files changed, 163 insertions(+), 112 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index f068330cb1..d98240904c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -55,8 +55,8 @@ class AudioMessageHelper @Inject constructor( private var amplitudeTicker: CountUpTimer? = null private var playbackTicker: CountUpTimer? = null - fun initializeRecorder(attachmentData: ContentAttachmentData) { - voiceRecorder.initializeRecord(attachmentData) + fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) { + voiceRecorder.initializeRecord(roomId, attachmentData) amplitudeList.clear() attachmentData.waveform?.let { amplitudeList.addAll(it) 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 5d3465ab2e..6f1210a584 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 @@ -943,7 +943,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) { - audioMessageHelper.initializeRecorder(attachmentData) + audioMessageHelper.initializeRecorder(room.roomId, attachmentData) setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) } } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index b28d76f176..9755a0b3fb 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -17,77 +17,24 @@ package im.vector.app.features.voice import android.content.Context -import android.media.MediaRecorder -import android.os.Build -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.util.md5 import java.io.File -import java.io.FileOutputStream import java.util.UUID abstract class AbstractVoiceRecorder( private val context: Context, - private val filenameExt: String, ) : VoiceRecorder { + private val outputDirectory: File by lazy { ensureAudioDirectory(context) } + protected var outputFile: File? = null - protected var mediaRecorder: MediaRecorder? = null - private var outputFile: File? = null - - abstract fun setOutputFormat(mediaRecorder: MediaRecorder) - - private fun init() { - createMediaRecorder().let { - it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) - setOutputFormat(it) - it.setAudioEncodingBitRate(24000) - it.setAudioSamplingRate(48000) - mediaRecorder = it + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + if (attachmentData != null) { + outputFile = attachmentData.findVoiceFile(outputDirectory) } } - private fun createMediaRecorder(): MediaRecorder { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaRecorder(context) - } else { - @Suppress("DEPRECATION") - MediaRecorder() - } - } - - override fun initializeRecord(attachmentData: ContentAttachmentData) { - outputFile = attachmentData.findVoiceFile(outputDirectory) - } - - override fun startRecord(roomId: String) { - init() - val fileName = "${UUID.randomUUID()}.$filenameExt" - val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { - mkdirs() - } - outputFile = File(outputDirectoryForRoom, fileName) - - val mr = mediaRecorder ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mr.setOutputFile(outputFile) - } else { - mr.setOutputFile(FileOutputStream(outputFile).fd) - } - mr.prepare() - mr.start() - } - - override fun stopRecord() { - mediaRecorder?.let { - // Can throw when the record is less than 1 second. - tryOrNull { it.stop() } - it.reset() - it.release() - } - mediaRecorder = null - } - override fun cancelRecord() { stopRecord() @@ -95,11 +42,15 @@ abstract class AbstractVoiceRecorder( outputFile = null } - override fun getMaxAmplitude(): Int { - return mediaRecorder?.maxAmplitude ?: 0 - } - override fun getVoiceMessageFile(): File? { return outputFile } + + protected fun createOutputFile(roomId: String): File { + val fileName = "${UUID.randomUUID()}.$fileNameExt" + val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { + mkdirs() + } + return File(outputDirectoryForRoom, fileName) + } } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt new file mode 100644 index 0000000000..bd30c38366 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 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.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.io.File + +/** + * VoiceRecorder abstraction to be used on Android versions >= [Build.VERSION_CODES.Q]. + */ +@RequiresApi(Build.VERSION_CODES.Q) +abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVoiceRecorder(context) { + + var mediaRecorder: MediaRecorder? = null + protected var nextOutputFile: File? = null + + private val audioSource: Int = MediaRecorder.AudioSource.DEFAULT + private val audioEncodingBitRate: Int = 24_000 + private val audioSamplingRate: Int = 48_000 + + abstract val outputFormat: Int // see MediaRecorder.OutputFormat + abstract val audioEncoder: Int // see MediaRecorder.AudioEncoder + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder = createMediaRecorder().apply { + setAudioSource(audioSource) + setOutputFormat() + setAudioEncodingBitRate(audioEncodingBitRate) + setAudioSamplingRate(audioSamplingRate) + } + setOutputFile(roomId) + } + + override fun startRecord(roomId: String) { + initializeRecord(roomId = roomId) + mediaRecorder?.prepare() + mediaRecorder?.start() + } + + override fun pauseRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.pause() } + } + + override fun resumeRecord() { + mediaRecorder?.resume() + } + + override fun stopRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.stop() } + mediaRecorder?.reset() + release() + } + + override fun cancelRecord() { + super.cancelRecord() + nextOutputFile?.delete() + nextOutputFile = null + } + + override fun getMaxAmplitude(): Int { + return mediaRecorder?.maxAmplitude ?: 0 + } + + protected open fun release() { + mediaRecorder?.release() + mediaRecorder = null + } + + fun setNextOutputFile(roomId: String) { + val mediaRecorder = mediaRecorder ?: return + nextOutputFile = createOutputFile(roomId) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaRecorder.setNextOutputFile(nextOutputFile) + } else { + mediaRecorder.setNextOutputFile(nextOutputFile?.outputStream()?.fd) + } + } + + private fun createMediaRecorder(): MediaRecorder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(context) + } else { + @Suppress("DEPRECATION") + MediaRecorder() + } + } + + private fun MediaRecorder.setOutputFormat() { + setOutputFormat(outputFormat) + setAudioEncoder(audioEncoder) + } + + private fun setOutputFile(roomId: String) { + val mediaRecorder = mediaRecorder ?: return + outputFile = outputFile ?: createOutputFile(roomId) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaRecorder.setOutputFile(outputFile) + } else { + mediaRecorder.setOutputFile(outputFile?.outputStream()?.fd) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 761aad2334..bf38e4adbf 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -22,11 +22,19 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData import java.io.File interface VoiceRecorder { + /** - * Initialize recording with a pre-recorded file. - * @param attachmentData data of the recorded file + * Audio file extension (eg. `mp4`). */ - fun initializeRecord(attachmentData: ContentAttachmentData) + val fileNameExt: String + + /** + * Initialize recording with an optional pre-recorded file. + * + * @param roomId id of the room to initialize record + * @param attachmentData data of the pre-recorded file, if any. + */ + fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData? = null) /** * Start the recording. diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index 1460f1a88f..13ddf60620 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -31,10 +31,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.content.ContentAttachmentData -import org.matrix.android.sdk.api.util.md5 -import java.io.File -import java.util.UUID import kotlin.coroutines.CoroutineContext /** @@ -44,16 +40,13 @@ class VoiceRecorderL( private val context: Context, coroutineContext: CoroutineContext, private val codec: OggOpusEncoder, -) : VoiceRecorder { +) : AbstractVoiceRecorder(context) { companion object { private val SAMPLE_RATE = SampleRate.Rate48kHz private const val BITRATE = 24 * 1024 } - private val outputDirectory: File by lazy { ensureAudioDirectory(context) } - private var outputFile: File? = null - private val recorderScope = CoroutineScope(coroutineContext) private var recordingJob: Job? = null @@ -65,6 +58,8 @@ class VoiceRecorderL( private var bufferSizeInShorts = 0 private var maxAmplitude = 0 + override val fileNameExt: String = "ogg" + private fun initializeCodec(filePath: String) { codec.init(filePath, SAMPLE_RATE) codec.setBitrate(BITRATE) @@ -86,19 +81,10 @@ class VoiceRecorderL( } } - override fun initializeRecord(attachmentData: ContentAttachmentData) { - outputFile = attachmentData.findVoiceFile(outputDirectory) - } - override fun startRecord(roomId: String) { - val fileName = "${UUID.randomUUID()}.ogg" - val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { - mkdirs() + outputFile = createOutputFile(roomId).also { + initializeCodec(it.absolutePath) } - val outputFile = File(outputDirectoryForRoom, fileName) - this.outputFile = outputFile - - initializeCodec(outputFile.absolutePath) recordingJob = recorderScope.launch { audioRecorder?.startRecording() @@ -140,19 +126,10 @@ class VoiceRecorderL( codec.release() } - override fun cancelRecord() { - outputFile?.delete() - outputFile = null - } - override fun getMaxAmplitude(): Int { return maxAmplitude } - override fun getVoiceMessageFile(): File? { - return outputFile - } - private fun createAudioRecord() { val channelConfig = AudioFormat.CHANNEL_IN_MONO val format = AudioFormat.ENCODING_PCM_16BIT diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt index 1eb850b8f5..f128673e27 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt @@ -20,26 +20,17 @@ import android.content.Context import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi -import org.matrix.android.sdk.api.extensions.tryOrNull /** - * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. It uses the native OPUS support on Android 10+. + * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. + * It uses the native OPUS support on Android 10+. */ @RequiresApi(Build.VERSION_CODES.Q) -class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") { +class VoiceRecorderQ(context: Context) : AbstractVoiceRecorderQ(context) { - override fun pauseRecord() { - // Can throw when the record is less than 1 second. - tryOrNull { mediaRecorder?.pause() } - } + // We can directly use OGG here + override val outputFormat = MediaRecorder.OutputFormat.OGG + override val audioEncoder = MediaRecorder.AudioEncoder.OPUS - override fun resumeRecord() { - mediaRecorder?.resume() - } - - override fun setOutputFormat(mediaRecorder: MediaRecorder) { - // We can directly use OGG here - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) - } + override val fileNameExt: String = "ogg" } From ad2bf8d1cefe9da56afa764509594ef45a8d2bf0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:45:01 +0200 Subject: [PATCH 0133/1068] Add VoiceBroadcastRecorder --- .../java/im/vector/app/core/di/VoiceModule.kt | 40 +++++++++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 83 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/di/VoiceModule.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt diff --git a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt new file mode 100644 index 0000000000..992ed80677 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt @@ -0,0 +1,40 @@ +/* + * 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.core.di + +import android.content.Context +import android.os.Build +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object VoiceModule { + @Provides + @Singleton + fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + VoiceBroadcastRecorder(context) + } else { + null + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt new file mode 100644 index 0000000000..2ec882a3d6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -0,0 +1,83 @@ +/* + * 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.voicebroadcast + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.io.File + +@RequiresApi(Build.VERSION_CODES.Q) +class VoiceBroadcastRecorder( + context: Context, +) : AbstractVoiceRecorderQ(context) { + + private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + + var listener: Listener? = null + + override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 + override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC + + override val fileNameExt: String = "mp4" + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder?.setMaxFileSize(maxFileSize) + mediaRecorder?.setOnInfoListener { _, what, _ -> + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() + else -> Unit // Nothing to do + } + } + } + + override fun stopRecord() { + super.stopRecord() + notifyOutputFileCreated() + listener = null + } + + override fun release() { + mediaRecorder?.setOnInfoListener(null) + super.release() + } + + private fun onMaxFileSizeApproaching(roomId: String) { + setNextOutputFile(roomId) + } + + private fun onNextOutputFileStarted() { + notifyOutputFileCreated() + } + + private fun notifyOutputFileCreated() { + outputFile?.let { + listener?.onVoiceMessageCreated(it) + outputFile = nextOutputFile + nextOutputFile = null + } + } + + fun interface Listener { + fun onVoiceMessageCreated(file: File) + } +} From 3ad245db8bbc5e9c5254909bc073ac0515e82d4e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:45:49 +0200 Subject: [PATCH 0134/1068] Trigger VoiceBroadcast recording actions --- .../usecase/PauseVoiceBroadcastUseCase.kt | 6 +-- .../usecase/ResumeVoiceBroadcastUseCase.kt | 6 +-- .../usecase/StartVoiceBroadcastUseCase.kt | 46 ++++++++++++++++--- .../usecase/StopVoiceBroadcastUseCase.kt | 9 ++-- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 6c779b7a50..1ba3f7aaa9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +32,7 @@ import javax.inject.Inject class PauseVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -66,6 +66,6 @@ class PauseVoiceBroadcastUseCase @Inject constructor( } private fun pauseRecording() { - audioMessageHelper.pauseRecording() + voiceBroadcastRecorder?.pauseRecord() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index fbbbc32612..69d3fe99a9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +32,7 @@ import javax.inject.Inject class ResumeVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -71,6 +71,6 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( } private fun resumeRecording() { - audioMessageHelper.resumeRecording() + voiceBroadcastRecorder?.resumeRecord() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 03dbbed5e4..f2d1df0ea4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -16,22 +16,33 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper +import android.content.Context +import android.os.Build +import androidx.core.content.FileProvider +import im.vector.app.core.resources.BuildMeta +import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.lib.multipicker.utils.toMultiPickerAudioType import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber +import java.io.File import javax.inject.Inject class StartVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, + private val context: Context, + private val buildMeta: BuildMeta, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -55,7 +66,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") - room.stateService().sendStateEvent( + val eventId = room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( @@ -64,10 +75,33 @@ class StartVoiceBroadcastUseCase @Inject constructor( ).toContent() ) - startRecording(room) + startRecording(room, eventId) } - private fun startRecording(room: Room) { - audioMessageHelper.startRecording(room.roomId) + private fun startRecording(room: Room, eventId: String) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> + sendVoiceFile(room, file, eventId) + } + voiceBroadcastRecorder?.startRecord(room.roomId) + } + } + + private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String) { + val outputFileUri = FileProvider.getUriForFile( + context, + buildMeta.applicationId + ".fileProvider", + voiceMessageFile, + "Voice message.${voiceMessageFile.extension}" + ) + val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return + if (audioType.duration > 1000) { + room.sendService().sendMedia( + attachment = audioType.toContentAttachmentData(isVoiceMessage = true), + compressBeforeSending = false, + roomIds = emptySet(), + relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index ed868fced8..7f352fc591 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,8 +16,9 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper +import android.os.Build import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +33,7 @@ import javax.inject.Inject class StopVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -67,6 +68,8 @@ class StopVoiceBroadcastUseCase @Inject constructor( } private fun stopRecording() { - audioMessageHelper.stopRecording() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + voiceBroadcastRecorder?.stopRecord() + } } } From aecb66015d19728378ac9452f97d9cd2b2e1c97e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 08:45:25 +0200 Subject: [PATCH 0135/1068] Move Voice Broadcast constants into dedicated object --- .../java/im/vector/app/core/extensions/TimelineEvent.kt | 6 ++++-- .../timeline/action/CheckIfCanRedactEventUseCase.kt | 4 ++-- .../room/detail/timeline/factory/TimelineItemFactory.kt | 4 ++-- .../detail/timeline/helper/TimelineDisplayableEvents.kt | 4 ++-- .../room/detail/timeline/helper/TimelineEventsGroups.kt | 4 ++-- .../features/voicebroadcast/VoiceBroadcastConstants.kt | 7 +++++-- .../model/MessageVoiceBroadcastInfoContent.kt | 4 ++-- .../features/voicebroadcast/model/VoiceBroadcastEvent.kt | 8 ++++---- .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 6 +++--- .../timeline/action/CheckIfCanRedactEventUseCaseTest.kt | 4 ++-- .../voicebroadcast/model/VoiceBroadcastEventTest.kt | 6 +++--- .../usecase/PauseVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/ResumeVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/StopVoiceBroadcastUseCaseTest.kt | 6 +++--- 18 files changed, 52 insertions(+), 47 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index cdb84387ce..5c3393416b 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -16,7 +16,7 @@ package im.vector.app.core.extensions -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean { fun TimelineEvent.getVectorLastMessageContent(): MessageContent? { // Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method return when (root.getClearType()) { - STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> { + (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + } else -> getLastMessageContent() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt index eda1929133..8cb82691d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.action import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -31,7 +31,7 @@ class CheckIfCanRedactEventUseCase @Inject constructor( val canRedactEventTypes: List = listOf( EventType.MESSAGE, EventType.STICKER, - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 0b8f95b4a1..31ff257214 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -21,7 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -89,7 +89,7 @@ class TimelineItemFactory @Inject constructor( // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params) - STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params) + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params) // Unhandled state event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 87844aba8e..2411cb3877 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.helper -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -52,7 +52,7 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index bd211a4513..13de456e84 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.TextUtils -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.extensions.orFalse @@ -59,7 +59,7 @@ class TimelineEventsGroups { val content = root.getClearContent() return when { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) - type == STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId + type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey else -> { null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index d7d74b08e9..8c005deb1f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -16,5 +16,8 @@ package im.vector.app.features.voicebroadcast -/** Voice Broadcast State Event. */ -const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" +object VoiceBroadcastConstants { + + /** Voice Broadcast State Event. */ + const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index b33d6cc4da..7e4a3d04be 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -18,7 +18,7 @@ package im.vector.app.features.voicebroadcast.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon import timber.log.Timber /** - * Content of the state event of type [STATE_ROOM_VOICE_BROADCAST_INFO]. + * Content of the state event of type [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO]. * * It contains general info related to a voice broadcast. */ diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt index c09a5712a8..d464a253d3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -16,14 +16,14 @@ package im.vector.app.features.voicebroadcast.model -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent /** - * [Event] wrapper for [STATE_ROOM_VOICE_BROADCAST_INFO] event type. + * [Event] wrapper for [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] event type. * Provides additional fields and functions related to voice broadcast. */ @JvmInline @@ -50,6 +50,6 @@ value class VoiceBroadcastEvent(val root: Event) { } /** - * Map a [STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. + * Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. */ -fun Event.asVoiceBroadcastEvent() = if (type == STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null +fun Event.asVoiceBroadcastEvent() = if (type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 1ba3f7aaa9..835a57c102 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -41,7 +41,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -54,7 +54,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 69d3fe99a9..2f03d4194c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -41,7 +41,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -59,7 +59,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index f2d1df0ea4..6be8e27345 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -21,7 +21,7 @@ import android.os.Build import androidx.core.content.FileProvider import im.vector.app.core.resources.BuildMeta import im.vector.app.features.attachments.toContentAttachmentData -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -51,7 +51,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( - setOf(STATE_ROOM_VOICE_BROADCAST_INFO), + setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), QueryStringValue.IsNotEmpty ) .mapNotNull { it.asVoiceBroadcastEvent() } @@ -67,7 +67,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") val eventId = room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 7f352fc591..85ffde0d02 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -17,7 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import android.os.Build -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -42,7 +42,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -56,7 +56,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt index fcb052cb2b..e2157c3af0 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.action -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.test.fakes.FakeActiveSessionHolder import io.mockk.mockk import org.amshove.kluent.shouldBe @@ -35,7 +35,7 @@ class CheckIfCanRedactEventUseCaseTest { @Test fun `given an event which can be redacted and owned by user when use case executes then the result is true`() { - val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, STATE_ROOM_VOICE_BROADCAST_INFO) + + val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO canRedactEventTypes.forEach { eventType -> diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt index 8865e870f0..8c3d24342f 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.model -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldNotBeNull @@ -48,7 +48,7 @@ class VoiceBroadcastEventTest { ) val event = Event( eventId = AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, content = content.toContent(), ) val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId) @@ -71,7 +71,7 @@ class VoiceBroadcastEventTest { relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), ) val event = Event( - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, content = content.toContent(), ) val expectedReference = content.relatesTo diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt index 3139f20cd4..7d950d1c73 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class PauseVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class PauseVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt index 23d506482b..215969a65d 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class ResumeVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class ResumeVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 398d6fedf0..a0336800ca 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -81,7 +81,7 @@ class StartVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -106,7 +106,7 @@ class StartVoiceBroadcastUseCaseTest { private fun givenAVoiceBroadcasts(voiceBroadcasts: List) { val events = voiceBroadcasts.map { Event( - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = it.userId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = it.state.value diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt index aa8dcddf30..dd69f12a4c 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class StopVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class StopVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, From ad730d55c1ed42c99ad71cbd3a22dcf8362ee09d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 08:56:14 +0200 Subject: [PATCH 0136/1068] Fix tests --- .../usecase/PauseVoiceBroadcastUseCaseTest.kt | 5 ++++- .../usecase/ResumeVoiceBroadcastUseCaseTest.kt | 5 ++++- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 11 ++++++++++- .../usecase/StopVoiceBroadcastUseCaseTest.kt | 5 ++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt index 7d950d1c73..5c42b26c54 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class PauseVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is paused or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt index 215969a65d..a1bc3a04ec 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class ResumeVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index a0336800ca..9fa6b7a450 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -17,14 +17,17 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeRoom import im.vector.app.test.fakes.FakeRoomService import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +47,13 @@ class StartVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( + fakeSession, + fakeVoiceBroadcastRecorder, + FakeContext().instance, + mockk() + ) @Test fun `given a room id with potential several existing voice broadcast states when calling execute then the voice broadcast is started or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt index dd69f12a4c..ee6b141bd9 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class StopVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is stopped or not`() = runTest { From c492fda00028a170bc24504d39239b8a64380b5d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 10:24:28 +0200 Subject: [PATCH 0137/1068] Change VoiceBroadcastRecorder as Interface --- .../java/im/vector/app/core/di/VoiceModule.kt | 3 +- .../voicebroadcast/VoiceBroadcastRecorder.kt | 60 +------------- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 78 +++++++++++++++++++ 3 files changed, 83 insertions(+), 58 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt diff --git a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt index 992ed80677..54d556ea91 100644 --- a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt @@ -23,6 +23,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ import javax.inject.Singleton @Module @@ -32,7 +33,7 @@ object VoiceModule { @Singleton fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - VoiceBroadcastRecorder(context) + VoiceBroadcastRecorderQ(context) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 2ec882a3d6..916f39106a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -16,66 +16,12 @@ package im.vector.app.features.voicebroadcast -import android.content.Context -import android.media.MediaRecorder -import android.os.Build -import androidx.annotation.RequiresApi -import im.vector.app.features.voice.AbstractVoiceRecorderQ -import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import im.vector.app.features.voice.VoiceRecorder import java.io.File -@RequiresApi(Build.VERSION_CODES.Q) -class VoiceBroadcastRecorder( - context: Context, -) : AbstractVoiceRecorderQ(context) { +interface VoiceBroadcastRecorder : VoiceRecorder { - private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s - - var listener: Listener? = null - - override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 - override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC - - override val fileNameExt: String = "mp4" - - override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { - super.initializeRecord(roomId, attachmentData) - mediaRecorder?.setMaxFileSize(maxFileSize) - mediaRecorder?.setOnInfoListener { _, what, _ -> - when (what) { - MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) - MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() - else -> Unit // Nothing to do - } - } - } - - override fun stopRecord() { - super.stopRecord() - notifyOutputFileCreated() - listener = null - } - - override fun release() { - mediaRecorder?.setOnInfoListener(null) - super.release() - } - - private fun onMaxFileSizeApproaching(roomId: String) { - setNextOutputFile(roomId) - } - - private fun onNextOutputFileStarted() { - notifyOutputFileCreated() - } - - private fun notifyOutputFileCreated() { - outputFile?.let { - listener?.onVoiceMessageCreated(it) - outputFile = nextOutputFile - nextOutputFile = null - } - } + var listener: Listener? fun interface Listener { fun onVoiceMessageCreated(file: File) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt new file mode 100644 index 0000000000..3aaad19a47 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -0,0 +1,78 @@ +/* + * 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.voicebroadcast + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.session.content.ContentAttachmentData + +@RequiresApi(Build.VERSION_CODES.Q) +class VoiceBroadcastRecorderQ( + context: Context, +) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { + + private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + + override var listener: VoiceBroadcastRecorder.Listener? = null + + override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 + override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC + + override val fileNameExt: String = "mp4" + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder?.setMaxFileSize(maxFileSize) + mediaRecorder?.setOnInfoListener { _, what, _ -> + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() + else -> Unit // Nothing to do + } + } + } + + override fun stopRecord() { + super.stopRecord() + notifyOutputFileCreated() + listener = null + } + + override fun release() { + mediaRecorder?.setOnInfoListener(null) + super.release() + } + + private fun onMaxFileSizeApproaching(roomId: String) { + setNextOutputFile(roomId) + } + + private fun onNextOutputFileStarted() { + notifyOutputFileCreated() + } + + private fun notifyOutputFileCreated() { + outputFile?.let { + listener?.onVoiceMessageCreated(it) + outputFile = nextOutputFile + nextOutputFile = null + } + } +} From 06bceef7c0f528927b1305ed5d046ca8a98fd4f0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 10:52:28 +0200 Subject: [PATCH 0138/1068] Add changelog --- changelog.d/7363.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7363.wip diff --git a/changelog.d/7363.wip b/changelog.d/7363.wip new file mode 100644 index 0000000000..b5a5f4c352 --- /dev/null +++ b/changelog.d/7363.wip @@ -0,0 +1 @@ +[Voice Broadcast] Record and send not aggregated voice messages to the room From fb9c747a20e8c1adb42bbe156fd915ffe2c5e0da Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 11:35:53 +0200 Subject: [PATCH 0139/1068] Reformat and add trailing commas --- .../sdk/api/session/room/send/SendService.kt | 6 +-- .../room/send/LocalEchoEventFactory.kt | 49 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 7bd6337548..53b49129c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -72,7 +72,7 @@ interface SendService { text: String, formattedText: String? = null, autoMarkdown: Boolean, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, ): Cancelable /** @@ -90,7 +90,7 @@ interface SendService { compressBeforeSending: Boolean, roomIds: Set, rootThreadEventId: String? = null, - relatesTo: RelationDefaultContent? = null + relatesTo: RelationDefaultContent? = null, ): Cancelable /** @@ -106,7 +106,7 @@ interface SendService { attachments: List, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, ): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 0fb2c9506a..1d7f624eba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -127,7 +127,7 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, ): Event { val content = if (newBodyFormattedText != null) { TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType) @@ -148,7 +148,7 @@ internal class LocalEchoEventFactory @Inject constructor( private fun createPollContent( question: String, options: List, - pollType: PollType + pollType: PollType, ): MessagePollContent { return MessagePollContent( unstablePollCreationInfo = PollCreationInfo( @@ -166,7 +166,7 @@ internal class LocalEchoEventFactory @Inject constructor( pollType: PollType, targetEventId: String, question: String, - options: List + options: List, ): Event { val newContent = MessagePollContent( relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), @@ -186,7 +186,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createPollReplyEvent( roomId: String, pollEventId: String, - answerId: String + answerId: String, ): Event { val content = MessagePollResponseContent( body = answerId, @@ -212,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, pollType: PollType, question: String, - options: List + options: List, ): Event { val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() @@ -229,7 +229,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createEndPollEvent( roomId: String, - eventId: String + eventId: String, ): Event { val content = MessageEndPollContent( relatesTo = RelationDefaultContent( @@ -254,7 +254,7 @@ internal class LocalEchoEventFactory @Inject constructor( latitude: Double, longitude: Double, uncertainty: Double?, - isUserLocation: Boolean + isUserLocation: Boolean, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN @@ -274,7 +274,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, latitude: Double, longitude: Double, - uncertainty: Double? + uncertainty: Double?, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val content = MessageBeaconLocationDataContent( @@ -305,7 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyText: String, autoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, ): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" @@ -359,7 +359,7 @@ internal class LocalEchoEventFactory @Inject constructor( attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId, - relatesTo + relatesTo, ) ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo) } @@ -385,7 +385,12 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createImageEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { var width = attachment.width var height = attachment.height @@ -415,7 +420,12 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createVideoEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -478,7 +488,12 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createFileEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -539,7 +554,7 @@ internal class LocalEchoEventFactory @Inject constructor( text: CharSequence, msgType: String, autoMarkdown: Boolean, - formattedText: String? + formattedText: String?, ): Event { val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown) return createEvent( @@ -568,7 +583,7 @@ internal class LocalEchoEventFactory @Inject constructor( replyTextFormatted: CharSequence?, autoMarkdown: Boolean, rootThreadEventId: String? = null, - showInThread: Boolean + showInThread: Boolean, ): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null @@ -614,7 +629,7 @@ internal class LocalEchoEventFactory @Inject constructor( type = RelationType.THREAD, eventId = rootThreadEventId, isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)) + inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)), ) /** @@ -760,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor( text: String, formattedText: String?, autoMarkdown: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, ): Event { val messageContent = quotedEvent.getLastMessageContent() val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body } From 64e6a2bfaba5aee8448a6e54617fd4b2d60e97c6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 12:23:02 +0200 Subject: [PATCH 0140/1068] Compute file size from chunk length --- .../voicebroadcast/VoiceBroadcastConstants.kt | 3 +++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 2 ++ .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 7 ++++++- .../model/MessageVoiceBroadcastInfoContent.kt | 2 +- .../usecase/StartVoiceBroadcastUseCase.kt | 16 +++++++--------- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 8c005deb1f..07bd94ca8c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -20,4 +20,7 @@ object VoiceBroadcastConstants { /** Voice Broadcast State Event. */ const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" + + /** Default voice broadcast chunk duration, in seconds */ + const val DEFAULT_CHUNK_LENGTH = 5 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 916f39106a..2668501a8d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -23,6 +23,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder { var listener: Listener? + fun startRecord(roomId: String, chunkLength: Int) + fun interface Listener { fun onVoiceMessageCreated(file: File) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 3aaad19a47..fe8222e840 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -28,7 +28,7 @@ class VoiceBroadcastRecorderQ( context: Context, ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { - private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + private var maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s override var listener: VoiceBroadcastRecorder.Listener? = null @@ -49,6 +49,11 @@ class VoiceBroadcastRecorderQ( } } + override fun startRecord(roomId: String, chunkLength: Int) { + maxFileSize = (chunkLength * 0.004166).toLong() // TODO change this approximate conversion + startRecord(roomId) + } + override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index 7e4a3d04be..5044bb5c34 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -41,7 +41,7 @@ data class MessageVoiceBroadcastInfoContent( /** The [VoiceBroadcastState] value. **/ @Json(name = "state") val voiceBroadcastStateStr: String = "", /** The length of the voice chunks in seconds. **/ - @Json(name = "chunk_length") val chunkLength: Long? = null, + @Json(name = "chunk_length") val chunkLength: Int? = null, ) : MessageContent { val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 6be8e27345..64f1dfb317 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -17,7 +17,6 @@ package im.vector.app.features.voicebroadcast.usecase import android.content.Context -import android.os.Build import androidx.core.content.FileProvider import im.vector.app.core.resources.BuildMeta import im.vector.app.features.attachments.toContentAttachmentData @@ -66,25 +65,24 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") + val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH // Todo Get the length from the room settings val eventId = room.stateService().sendStateEvent( eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, - chunkLength = 5L, // TODO Get length from voice broadcast settings + chunkLength = chunkLength, ).toContent() ) - startRecording(room, eventId) + startRecording(room, eventId, chunkLength) } - private fun startRecording(room: Room, eventId: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> - sendVoiceFile(room, file, eventId) - } - voiceBroadcastRecorder?.startRecord(room.roomId) + private fun startRecording(room: Room, eventId: String, chunkLength: Int) { + voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> + sendVoiceFile(room, file, eventId) } + voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength) } private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String) { From 9d35e81db7a8e3b7206b2afadc94435863145c99 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:37:59 +0200 Subject: [PATCH 0141/1068] Compute max file size from chunk length --- .../im/vector/app/features/voice/AbstractVoiceRecorderQ.kt | 2 +- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt index bd30c38366..428b3c578b 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -34,8 +34,8 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo protected var nextOutputFile: File? = null private val audioSource: Int = MediaRecorder.AudioSource.DEFAULT - private val audioEncodingBitRate: Int = 24_000 private val audioSamplingRate: Int = 48_000 + protected val audioEncodingBitRate: Int = 24_000 abstract val outputFormat: Int // see MediaRecorder.OutputFormat abstract val audioEncoder: Int // see MediaRecorder.AudioEncoder diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 07bd94ca8c..83d83b167e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -22,5 +22,5 @@ object VoiceBroadcastConstants { const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" /** Default voice broadcast chunk duration, in seconds */ - const val DEFAULT_CHUNK_LENGTH = 5 + const val DEFAULT_CHUNK_LENGTH = 30 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index fe8222e840..620db721c9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -28,7 +28,7 @@ class VoiceBroadcastRecorderQ( context: Context, ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { - private var maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + private var maxFileSize = 0L // zero or negative for no limit override var listener: VoiceBroadcastRecorder.Listener? = null @@ -50,7 +50,7 @@ class VoiceBroadcastRecorderQ( } override fun startRecord(roomId: String, chunkLength: Int) { - maxFileSize = (chunkLength * 0.004166).toLong() // TODO change this approximate conversion + maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() startRecord(roomId) } From 62596b38c78e4bc569777ce2371f43ea1064bb0f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:40:22 +0200 Subject: [PATCH 0142/1068] Pause recording when the composer is not visible anymore --- .../detail/composer/MessageComposerFragment.kt | 16 ++++++++++++---- .../detail/composer/MessageComposerViewModel.kt | 15 +++++++++++++++ .../detail/composer/MessageComposerViewState.kt | 9 +++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index b3abfa480e..4721b81571 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -227,10 +227,18 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onPause() { super.onPause() - if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { - // we're rotating, maintain any active recordings - } else { - messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) + withState(messageComposerViewModel) { + when { + it.isVoiceRecording && requireActivity().isChangingConfigurations -> { + // we're rotating, maintain any active recordings + } + // TODO remove this when there will be a recording indicator outside of the timeline + // Pause voice broadcast if the timeline is not shown anymore + it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Pause) + else -> { + messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) + } + } } } 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 6f1210a584..eef06d11b7 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 @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.composer +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -40,8 +41,11 @@ import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -90,6 +94,7 @@ class MessageComposerViewModel @AssistedInject constructor( init { loadDraftIfAny() observePowerLevelAndEncryption() + observeVoiceBroadcast() subscribeToStateInternal() } @@ -182,6 +187,16 @@ class MessageComposerViewModel @AssistedInject constructor( } } + private fun observeVoiceBroadcast() { + room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + .asFlow() + .unwrap() + .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .setOnEach { + copy(voiceBroadcastState = it) + } + } + private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) { room.getTimelineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) } 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 47a7122584..0df1dbebd8 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 im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import kotlin.random.Random @@ -67,6 +68,7 @@ data class MessageComposerViewState( val startsThread: Boolean = false, val sendMode: SendMode = SendMode.Regular("", false), val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle, + val voiceBroadcastState: VoiceBroadcastState? = null, val text: CharSequence? = null, ) : MavericksState { @@ -77,6 +79,13 @@ data class MessageComposerViewState( is VoiceMessageRecorderView.RecordingUiState.Recording -> true } + val isVoiceBroadcasting = when (voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED -> true + else -> false + } + val isVoiceMessageIdle = !isVoiceRecording val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording From 3a951f207604d638bb7ab66453e4d4162e84b32b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:49:27 +0200 Subject: [PATCH 0143/1068] Add punctuation to kdoc --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 83d83b167e..bd37251dd4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -21,6 +21,6 @@ object VoiceBroadcastConstants { /** Voice Broadcast State Event. */ const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" - /** Default voice broadcast chunk duration, in seconds */ + /** Default voice broadcast chunk duration, in seconds. */ const val DEFAULT_CHUNK_LENGTH = 30 } From 8cfe6b13639158011e63b40e2592aa770316e5a0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 17 Oct 2022 23:46:28 +0100 Subject: [PATCH 0144/1068] Wording updates --- library/ui-strings/src/main/res/values/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e28ebb31c6..7fe8dd8d08 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3386,12 +3386,12 @@ A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your intent connection(s); Your device(s); The other device is already signed in. The other device must be signed in. - The QR code scanned is invalid. + That QR code is invalid. The sign in was cancelled on the other device. The homeserver doesn\'t support sign in with QR code. - Open ${app_name} on your other device - Go to Settings -> Security & Privacy -> Show All Sessions - Select \'Show QR code in this device\' + Open the app on your other device + Go to Settings -> Security & Privacy + Select \'Show QR code\' Start at the sign in screen Select \'Sign in with QR code\' Start at the sign in screen From 7017cb97e94d10ebdfa5cfa4ce47a1b0f1c21173 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:04:02 +0000 Subject: [PATCH 0145/1068] Bump michaelkaye/setup-matrix-synapse from 1.0.3 to 1.0.4 Bumps [michaelkaye/setup-matrix-synapse](https://github.com/michaelkaye/setup-matrix-synapse) from 1.0.3 to 1.0.4. - [Release notes](https://github.com/michaelkaye/setup-matrix-synapse/releases) - [Commits](https://github.com/michaelkaye/setup-matrix-synapse/compare/v1.0.3...v1.0.4) --- updated-dependencies: - dependency-name: michaelkaye/setup-matrix-synapse dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/post-pr.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index bf948064ed..628e12593c 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -52,7 +52,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + uses: michaelkaye/setup-matrix-synapse@v1.0.4 with: uploadLogs: true httpPort: 8080 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1816fe3a78..bb16d8abe8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.8 - - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + - uses: michaelkaye/setup-matrix-synapse@v1.0.4 with: uploadLogs: true httpPort: 8080 From 5c4c3c92d4c294ec101d956c96117b70f51c4e49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:08:08 +0000 Subject: [PATCH 0146/1068] Bump flipper from 0.170.0 to 0.171.0 Bumps `flipper` from 0.170.0 to 0.171.0. Updates `flipper` from 0.170.0 to 0.171.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.170.0...v0.171.0) Updates `flipper-network-plugin` from 0.170.0 to 0.171.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.170.0...v0.171.0) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index d60283e825..7ca95b0ba1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -18,7 +18,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.170.0" +def flipper = "0.171.0" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From cf1c7515fb87283915412c7718917a0d9902221f Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 00:35:13 +0100 Subject: [PATCH 0147/1068] Automatically try again on partial failed QR scan --- .../features/login/qr/QrCodeLoginInstructionsFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt index efd23f2530..40fcbbbb85 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt @@ -63,6 +63,7 @@ class QrCodeLoginInstructionsFragment : VectorBaseFragment From a3126b0026dc70ad6c9e2cf12a11fe3ba0944179 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 00:35:35 +0100 Subject: [PATCH 0148/1068] Progress to status screen on failure --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 4134dc8ab2..1a8ed945c0 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -125,6 +125,8 @@ class QrCodeLoginViewModel @AssistedInject constructor( } private fun onFailed(reason: RendezvousFailureReason) { + _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen) + setState { copy( connectionStatus = QrCodeLoginConnectionStatus.Failed(reason, reason.canRetry) From 8a62dfb34aca1c3ab52d05d240ea74cc2b5a6d54 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 00:35:43 +0100 Subject: [PATCH 0149/1068] Lint --- .../matrix/android/sdk/api/rendezvous/RendezvousTransport.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt index 5daf906930..81632e951a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt @@ -22,11 +22,15 @@ import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails interface RendezvousTransport { var ready: Boolean + @Throws(RendezvousError::class) suspend fun details(): RendezvousTransportDetails + @Throws(RendezvousError::class) suspend fun send(contentType: MediaType, data: ByteArray) + @Throws(RendezvousError::class) suspend fun receive(): ByteArray? + suspend fun close() } From afa55649c53e31511758cd09030d98b92869f540 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 00:52:02 +0000 Subject: [PATCH 0150/1068] Bump play-services-location from 20.0.0 to 21.0.0 Bumps play-services-location from 20.0.0 to 21.0.0. --- updated-dependencies: - dependency-name: com.google.android.gms:play-services-location dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- vector-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index ca77e4b86f..66fdf3fbf4 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -372,7 +372,7 @@ dependencies { debugImplementation 'com.facebook.soloader:soloader:0.10.4' debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0" - gplayImplementation "com.google.android.gms:play-services-location:20.0.0" + gplayImplementation "com.google.android.gms:play-services-location:21.0.0" // UnifiedPush gplay flavor only gplayImplementation('com.google.firebase:firebase-messaging:23.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' From ba822d8231783174ec493e935edefc29f44c5d88 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Mon, 17 Oct 2022 19:49:00 +0000 Subject: [PATCH 0151/1068] Translated using Weblate (German) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index a8e24369b5..837a00e30b 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2771,4 +2771,37 @@ ${app_name} braucht die Berechtigung, um Benachrichtigungen anzuzeigen. Benachrichtigungen können deine Nachrichten, Einladungen etc. anzeigen. \n \nBitte erlaube den Zugriff im nächsten Dialog, damit Benachrichtigungen angezeigt werden können. + Bitte vergewissere dich, dass du den Ursprung dieses Codes kennst. Durch die Verbindung beider Geräte, gewährst du Zugriff auf deinen gesamten Account. + Bestätigen + Nochmal versuchen + Keine Übereinstimmung\? + Du wirst angemeldet + Stelle Verbindung zum Gerät her + QR-Code scannen + Mobiles Gerät anmelden\? + QR-Code auf diesem Gerät anzeigen + Wähle \'QR-Code scannen\' + Beginne auf dem Anmeldebildschirm + Wähle \'Mit QR-Code anmelden\' + Beginne auf dem Anmeldebildschirm + Wähle \'QR-Code auf diesem Gerät anzeigen\' + Gehe zu Einstellungen -> Sicherheit und Privatsphäre -> Alle Sitzungen anzeigen + Öffne ${app_name} auf deinem anderen Gerät + Die Anfrage wurde auf dem anderen Gerät abgelehnt. + Die Verbindung konnte nicht in der erforderlichen Zeit hergestellt werden. + Verbindung mit diesem Gerät nicht unterstützt. + Verbindung fehlgeschlagen + Überprüfe dein angemeldetes Gerät. Der unten gezeigte Code sollte angezeigt werden. Bestätige, dass beide Codes übereinstimmen: + Sichere Verbindung hergestellt + Scanne den unten angezeigten QR-Code mit deinem nicht angemeldeten Gerät. + Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code zu scannen: + Mit QR-Code anmelden + Benutze die Kamera auf diesem Gerät um den vom anderen Gerät angezeigten QR-Code zu scannen: + QR-Code scannen + 3 + 2 + 1 + Du kannst dieses Gerät benutzen um ein anderes Gerät per QR-Code anzumelden. Dafür gibt es zwei Wege: + Mit QR-Code anmelden + QR-Code scannen \ No newline at end of file From 1db3d69aebf20d14faf223ee0f0da5648b58e769 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 08:58:21 +0200 Subject: [PATCH 0152/1068] Change chunk_length type in unit test --- .../features/voicebroadcast/model/VoiceBroadcastEventTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt index 8c3d24342f..ca2e8320c9 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent private const val AN_EVENT_ID = "event_id" private const val A_REFERENCED_EVENT_ID = "event_id_ref" -private const val A_CHUNK_LENGTH = 3_600L +private const val A_CHUNK_LENGTH = 30 class VoiceBroadcastEventTest { From f297117df2fe66c61e8589e73bc88cf7a99586bb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 08:48:28 +0100 Subject: [PATCH 0153/1068] Use mutex --- .../channels/ECDHRendezvousChannel.kt | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 2f368d6520..19c46db34d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -19,6 +19,8 @@ package org.matrix.android.sdk.api.rendezvous.channels import android.util.Base64 import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import okhttp3.MediaType.Companion.toMediaType import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.rendezvous.RendezvousChannel @@ -71,6 +73,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu @Json val iv: String? = null ) + private var olmSASMutex = Mutex() private var olmSAS: OlmSAS? private val ourPublicKey: ByteArray private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) @@ -87,45 +90,44 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu @Throws(RendezvousError::class) override suspend fun connect(): String { - olmSAS ?.let { olmSAS -> - val isInitiator = theirPublicKey == null + val sas = olmSAS ?: throw RendezvousError("Channel closed", RendezvousFailureReason.Unknown) + val isInitiator = theirPublicKey == null - if (isInitiator) { - Timber.tag(TAG).i("Waiting for other device to send their public key") - val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError) + if (isInitiator) { + Timber.tag(TAG).i("Waiting for other device to send their public key") + val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError) - if (res.key == null) { - throw RendezvousError( - "Unsupported algorithm: ${res.algorithm}", - RendezvousFailureReason.UnsupportedAlgorithm, - ) - } - theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) - } else { - // send our public key unencrypted - Timber.tag(TAG).i("Sending public key") - send( - ECDHPayload( - algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, - key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) - ) + if (res.key == null) { + throw RendezvousError( + "Unsupported algorithm: ${res.algorithm}", + RendezvousFailureReason.UnsupportedAlgorithm, ) } + theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) + } else { + // send our public key unencrypted + Timber.tag(TAG).i("Sending public key") + send( + ECDHPayload( + algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, + key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) + ) + ) + } - synchronized(olmSAS) { - olmSAS.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) - olmSAS.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + olmSASMutex.withLock { + sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) - val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) - val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) - val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" + val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) + val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) + val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" - aesKey = olmSAS.generateShortCode(aesInfo, 32) + aesKey = sas.generateShortCode(aesInfo, 32) - val rawChecksum = olmSAS.generateShortCode(aesInfo, 5) - return getDecimalCodeRepresentation(rawChecksum) - } - } ?: throw RuntimeException("Channel closed") + val rawChecksum = sas.generateShortCode(aesInfo, 5) + return getDecimalCodeRepresentation(rawChecksum) + } } private suspend fun send(payload: ECDHPayload) { @@ -154,12 +156,11 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } override suspend fun close() { - olmSAS ?.let { - synchronized(it) { - // this does a double release check already so we don't re-check ourselves - it.releaseSas() - olmSAS = null - } + val sas = olmSAS ?: throw IllegalStateException("Channel already closed") + olmSASMutex.withLock { + // this does a double release check already so we don't re-check ourselves + sas.releaseSas() + olmSAS = null } transport.close() } From 6d6d2de08b918bebf101adb8521914d5204b700a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 18 Oct 2022 10:00:18 +0200 Subject: [PATCH 0154/1068] Fix message send even if the step `ui-tests` is successful. Jobs `codecov-units` and `integration-tests` do not exist anymore. --- .github/workflows/post-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index bf948064ed..b618519b0d 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -94,7 +94,7 @@ jobs: needs: - should-i-run - ui-tests - if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success')) + if: always() && (needs.should-i-run.result == 'success' ) && (needs.ui-tests.result != 'success') # No concurrency required, runs every time on a schedule. steps: - uses: michaelkaye/matrix-hookshot-action@v1.0.0 From a1d2944c324c37bdcf9c92e0ec02864ce6781d71 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 09:08:40 +0100 Subject: [PATCH 0155/1068] Always check master key when provided by verifying device --- .../android/sdk/api/rendezvous/Rendezvous.kt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt index e41a72db94..f724ac4b62 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -165,21 +165,25 @@ class Rendezvous( } verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice -> - // check master key againt what the homeserver told us - crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()?.let { localMasterKey -> - if (localMasterKey.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { - Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") - // inform the other side - send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) - throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) - } - // set other device as verified - Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") - crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + // verifying device provided us with a master key, so use it to check integrity - Timber.tag(TAG).i("Setting master key as trusted") - crypto.crossSigningService().markMyMasterKeyAsTrusted() - } ?: Timber.tag(TAG).w("No local master key so not verifying") + // see what the homeserver told us + val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey() + + // n.b. if no local master key this is a problem, as well as it not matching + if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { + Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") + // inform the other side + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } + + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + Timber.tag(TAG).i("Setting master key as trusted") + crypto.crossSigningService().markMyMasterKeyAsTrusted() } ?: run { // set other device as verified anyway Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") From 57a8dd4a1f61b50e180830d96264d5c3b1f1fe72 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 09:31:13 +0100 Subject: [PATCH 0156/1068] Whitespce --- .../sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt | 4 ++-- .../transports/SimpleHttpRendezvousTransport.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 19c46db34d..f7da0efa92 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -81,7 +81,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu private var aesKey: ByteArray? = null init { - theirPublicKeyBase64 ?.let { + theirPublicKeyBase64?.let { theirPublicKey = Base64.decode(it, Base64.NO_WRAP) } olmSAS = OlmSAS() @@ -142,7 +142,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu } private suspend fun receiveAsPayload(): ECDHPayload? { - transport.receive()?.toString(Charsets.UTF_8) ?.let { + transport.receive()?.toString(Charsets.UTF_8)?.let { return ecdhAdapter.fromJson(it) } ?: return null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 50cebae12d..3f7cfe786e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -69,7 +69,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor .method(method, data.toRequestBody()) .header("content-type", contentType.toString()) - etag ?.let { + etag?.let { request.header("if-match", it) } @@ -85,7 +85,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor if (method == "POST") { val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response") - response.header("expires") ?.let { + response.header("expires")?.let { val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz") expiresAt = format.parse(it) } @@ -108,7 +108,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor .url(uri) .get() - etag ?.let { + etag?.let { request.header("if-none-match", it) } @@ -154,7 +154,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor cancelled = true ready = false - uri ?.let { + uri?.let { try { val httpClient = okhttp3.OkHttpClient.Builder().build() val request = Request.Builder() From 376cd1cb367c8fa3d3b9f9f2da898a56e0f3636e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 09:34:28 +0100 Subject: [PATCH 0157/1068] Missing throws --- .../api/rendezvous/transports/SimpleHttpRendezvousTransport.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 3f7cfe786e..7a5cbe5424 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -54,6 +54,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor return SimpleHttpRendezvousTransportDetails(uri) } + @Throws(RendezvousError::class) override suspend fun send(contentType: MediaType, data: ByteArray) { if (cancelled) { throw IllegalStateException("Rendezvous cancelled") @@ -96,6 +97,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor } } + @Throws(RendezvousError::class) override suspend fun receive(): ByteArray? { if (cancelled) { throw IllegalStateException("Rendezvous cancelled") From 6f5fefba56389896ef7bb5b4d0e2e786fb957e1f Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Tue, 18 Oct 2022 10:44:20 +0200 Subject: [PATCH 0158/1068] lint --- .../org/matrix/android/sdk/internal/auth/version/Versions.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index 9ed8901683..1245d8df4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -88,7 +88,6 @@ internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean { val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773) - } internal fun Versions.doesServerSupportQrCodeLogin(): Boolean { From 9fb0db3129ff6521ac86aba4bee29c12e6d45d5b Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:07:12 +0100 Subject: [PATCH 0159/1068] Update library/ui-strings/src/main/res/values/strings.xml Co-authored-by: Benoit Marty --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7fe8dd8d08..7b6b9b914d 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3383,7 +3383,7 @@ The linking wasn’t completed in the required time. The request was denied on the other device. The request failed. - A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your intent connection(s); Your device(s); + A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s); The other device is already signed in. The other device must be signed in. That QR code is invalid. From 0d1df3f66e5587ba299b909183fa464673694489 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:08:09 +0100 Subject: [PATCH 0160/1068] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt Co-authored-by: Benoit Marty --- .../org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index be79569164..9976c7a8e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -45,7 +45,7 @@ interface RendezvousChannel { suspend fun receive(): ByteArray? /** - * @returns closes the channel and cleans up + * Closes the channel and cleans up. */ suspend fun close() } From 8530f8f28049dae1be38c2cf0503e8cfac04f2f6 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:09:06 +0100 Subject: [PATCH 0161/1068] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt Co-authored-by: Benoit Marty --- .../sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index f7da0efa92..b21d89059b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -73,7 +73,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu @Json val iv: String? = null ) - private var olmSASMutex = Mutex() + private val olmSASMutex = Mutex() private var olmSAS: OlmSAS? private val ourPublicKey: ByteArray private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) From a83fb8bf83c10759c1e25e126e1fae5319cc4dad Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:09:17 +0100 Subject: [PATCH 0162/1068] Update matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt Co-authored-by: Benoit Marty --- .../org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt index 9976c7a8e4..0956a5b0a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.rendezvous.model.RendezvousError * Representation of a rendezvous channel such as that described by MSC3903. */ interface RendezvousChannel { - var transport: RendezvousTransport + val transport: RendezvousTransport /** * @returns the checksum/confirmation digits to be shown to the user From 916ae654e790bc001a1f5f86ef30d32657da952d Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:11:41 +0100 Subject: [PATCH 0163/1068] Don't log whole QR code --- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 1a8ed945c0..9bf3e955d3 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -76,7 +76,7 @@ class QrCodeLoginViewModel @AssistedInject constructor( } private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) { - Timber.tag(TAG).d("Scanned code: ${action.qrCode}") + Timber.tag(TAG).d("Scanned code of length ${action.qrCode.length}") val rendezvous = try { Rendezvous.buildChannelFromCode(action.qrCode) } catch (t: Throwable) { Timber.tag(TAG).e(t, "Error occurred during sign in") From 811d6d87ae50595726494abe772a8931d27da0c2 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:23:21 +0100 Subject: [PATCH 0164/1068] Reuse getDecimalCodeRepresentation from SAS instead of duplicating code --- .../channels/ECDHRendezvousChannel.kt | 17 ++---- .../SASDefaultVerificationTransaction.kt | 54 +++++++++---------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index b21d89059b..6c45aacfaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.rendezvous.RendezvousTransport import org.matrix.android.sdk.api.rendezvous.model.RendezvousError import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm import org.matrix.android.sdk.api.util.MatrixJsonParser -import org.matrix.android.sdk.internal.extensions.toUnsignedInt +import org.matrix.android.sdk.internal.crypto.verification.SASDefaultVerificationTransaction import org.matrix.olm.OlmSAS import timber.log.Timber import java.security.SecureRandom @@ -48,20 +48,9 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu private const val KEY_SPEC = "AES" private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value - // n.b. we are only aver processing byte array that we have generated, so we can make assumptions about the length + // this is the same representation as for SAS but we delimit by dashes instead of spaces for readability private fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - val b0 = byteArray[0].toUnsignedInt() // need unsigned byte - val b1 = byteArray[1].toUnsignedInt() // need unsigned byte - val b2 = byteArray[2].toUnsignedInt() // need unsigned byte - val b3 = byteArray[3].toUnsignedInt() // need unsigned byte - val b4 = byteArray[4].toUnsignedInt() // need unsigned byte - // (B0 << 5 | B1 >> 3) + 1000 - val first = (b0.shl(5) or b1.shr(3)) + 1000 - // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 - val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 - // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 - val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 - return "$first-$second-$third" + return SASDefaultVerificationTransaction.getDecimalCodeRepresentation(byteArray).replace(" ", "-") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 1cbaff059a..b306288c5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -82,6 +82,33 @@ internal abstract class SASDefaultVerificationTransaction( // older devices have limited support of emoji but SDK offers images for the 64 verification emojis // so always send that we support EMOJI val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL) + + /** + * decimal: generate five bytes by using HKDF. + * Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive), + * and add 1000 (resulting in a number between 1000 and 9191 inclusive). + * Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers. + * In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000, + * the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000. + * (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic, + * and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.) + * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers, + * or with the three numbers on separate lines. + */ + fun getDecimalCodeRepresentation(byteArray: ByteArray): String { + val b0 = byteArray[0].toUnsignedInt() // need unsigned byte + val b1 = byteArray[1].toUnsignedInt() // need unsigned byte + val b2 = byteArray[2].toUnsignedInt() // need unsigned byte + val b3 = byteArray[3].toUnsignedInt() // need unsigned byte + val b4 = byteArray[4].toUnsignedInt() // need unsigned byte + // (B0 << 5 | B1 >> 3) + 1000 + val first = (b0.shl(5) or b1.shr(3)) + 1000 + // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 + val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 + // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 + val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 + return "$first $second $third" + } } override var state: VerificationTxState = VerificationTxState.None @@ -371,33 +398,6 @@ internal abstract class SASDefaultVerificationTransaction( return getDecimalCodeRepresentation(shortCodeBytes!!) } - /** - * decimal: generate five bytes by using HKDF. - * Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive), - * and add 1000 (resulting in a number between 1000 and 9191 inclusive). - * Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers. - * In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000, - * the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000. - * (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic, - * and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.) - * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers, - * or with the three numbers on separate lines. - */ - fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - val b0 = byteArray[0].toUnsignedInt() // need unsigned byte - val b1 = byteArray[1].toUnsignedInt() // need unsigned byte - val b2 = byteArray[2].toUnsignedInt() // need unsigned byte - val b3 = byteArray[3].toUnsignedInt() // need unsigned byte - val b4 = byteArray[4].toUnsignedInt() // need unsigned byte - // (B0 << 5 | B1 >> 3) + 1000 - val first = (b0.shl(5) or b1.shr(3)) + 1000 - // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000 - val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 - // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 - val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 - return "$first $second $third" - } - override fun getEmojiCodeRepresentation(): List { return getEmojiCodeRepresentation(shortCodeBytes!!) } From f7e0a198335b9830af68404d522c834319adb784 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:29:48 +0100 Subject: [PATCH 0165/1068] Remove redundant annotations --- .../api/rendezvous/channels/ECDHRendezvousChannel.kt | 8 ++++---- .../sdk/api/rendezvous/model/ECDHRendezvous.kt | 7 +++---- .../sdk/api/rendezvous/model/ECDHRendezvousCode.kt | 5 ++--- .../android/sdk/api/rendezvous/model/Payload.kt | 12 ++++++------ .../rendezvous/model/RendezvousTransportDetails.kt | 3 +-- .../model/SimpleHttpRendezvousTransportDetails.kt | 3 +-- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 6c45aacfaa..4bbf34280c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -56,10 +56,10 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu @JsonClass(generateAdapter = true) internal data class ECDHPayload( - @Json val algorithm: SecureRendezvousChannelAlgorithm? = null, - @Json val key: String? = null, - @Json val ciphertext: String? = null, - @Json val iv: String? = null + val algorithm: SecureRendezvousChannelAlgorithm? = null, + val key: String? = null, + val ciphertext: String? = null, + val iv: String? = null ) private val olmSASMutex = Mutex() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt index 0840e1ca2e..55bac6397e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt @@ -16,12 +16,11 @@ package org.matrix.android.sdk.api.rendezvous.model -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ECDHRendezvous( - @Json val transport: SimpleHttpRendezvousTransportDetails, - @Json val algorithm: SecureRendezvousChannelAlgorithm, - @Json val key: String + val transport: SimpleHttpRendezvousTransportDetails, + val algorithm: SecureRendezvousChannelAlgorithm, + val key: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt index 410c5c1036..575b5d4bfd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt @@ -16,11 +16,10 @@ package org.matrix.android.sdk.api.rendezvous.model -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ECDHRendezvousCode( - @Json val intent: RendezvousIntent, - @Json val rendezvous: ECDHRendezvous + val intent: RendezvousIntent, + val rendezvous: ECDHRendezvous ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt index 593177e625..04631ce959 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt @@ -21,12 +21,12 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class Payload( - @Json val type: PayloadType, - @Json val intent: RendezvousIntent? = null, - @Json val outcome: Outcome? = null, - @Json val protocols: List? = null, - @Json val protocol: Protocol? = null, - @Json val homeserver: String? = null, + val type: PayloadType, + val intent: RendezvousIntent? = null, + val outcome: Outcome? = null, + val protocols: List? = null, + val protocol: Protocol? = null, + val homeserver: String? = null, @Json(name = "login_token") val loginToken: String? = null, @Json(name = "device_id") val deviceId: String? = null, @Json(name = "device_key") val deviceKey: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt index 55b3bbb5d9..1bde43ab7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt @@ -16,10 +16,9 @@ package org.matrix.android.sdk.api.rendezvous.model -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) open class RendezvousTransportDetails( - @Json val type: RendezvousTransportType + val type: RendezvousTransportType ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt index 70a441d760..049aa8b756 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt @@ -16,10 +16,9 @@ package org.matrix.android.sdk.api.rendezvous.model -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class SimpleHttpRendezvousTransportDetails( - @Json val uri: String + val uri: String ) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) From 400118ed3ec8eb19bb4ba6011f5b3f5c6c0816b7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:03:57 +0200 Subject: [PATCH 0166/1068] Remove useless Android API checks --- .../app/features/voice/AbstractVoiceRecorderQ.kt | 12 ++---------- .../usecase/StopVoiceBroadcastUseCase.kt | 5 +---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt index 428b3c578b..0d8373870f 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -91,11 +91,7 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo fun setNextOutputFile(roomId: String) { val mediaRecorder = mediaRecorder ?: return nextOutputFile = createOutputFile(roomId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mediaRecorder.setNextOutputFile(nextOutputFile) - } else { - mediaRecorder.setNextOutputFile(nextOutputFile?.outputStream()?.fd) - } + mediaRecorder.setNextOutputFile(nextOutputFile) } private fun createMediaRecorder(): MediaRecorder { @@ -115,10 +111,6 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo private fun setOutputFile(roomId: String) { val mediaRecorder = mediaRecorder ?: return outputFile = outputFile ?: createOutputFile(roomId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mediaRecorder.setOutputFile(outputFile) - } else { - mediaRecorder.setOutputFile(outputFile?.outputStream()?.fd) - } + mediaRecorder.setOutputFile(outputFile) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 85ffde0d02..6eefa06979 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,7 +16,6 @@ package im.vector.app.features.voicebroadcast.usecase -import android.os.Build import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent @@ -68,8 +67,6 @@ class StopVoiceBroadcastUseCase @Inject constructor( } private fun stopRecording() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - voiceBroadcastRecorder?.stopRecord() - } + voiceBroadcastRecorder?.stopRecord() } } From def9fc07bbd5b8404bd0d70dd03d0bcfaa03c555 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:08:17 +0200 Subject: [PATCH 0167/1068] Revert AudioMessageHelper.pauseRecording --- .../room/detail/composer/AudioMessageHelper.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index d98240904c..bede02c17f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -114,13 +114,9 @@ class AudioMessageHelper @Inject constructor( * When entering in playback mode actually. */ fun pauseRecording() { - voiceRecorder.pauseRecord() - pauseRecordingAmplitudes() - } - - fun resumeRecording() { - voiceRecorder.resumeRecord() - resumeRecordingAmplitudes() + // TODO should we pause instead of stop? + voiceRecorder.stopRecord() + stopRecordingAmplitudes() } fun deleteRecording() { @@ -226,10 +222,6 @@ class AudioMessageHelper @Inject constructor( } } - private fun pauseRecordingAmplitudes() { - amplitudeTicker?.pause() - } - private fun resumeRecordingAmplitudes() { amplitudeTicker?.resume() } From 92bd8cdcfe71bf01f780f14bf1a41c0765c5b977 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:09:48 +0200 Subject: [PATCH 0168/1068] Voice Broadcast - Remove check on voice message minimum duration --- .../usecase/StartVoiceBroadcastUseCase.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 64f1dfb317..81a98f6fce 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -93,13 +93,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( "Voice message.${voiceMessageFile.extension}" ) val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return - if (audioType.duration > 1000) { - room.sendService().sendMedia( - attachment = audioType.toContentAttachmentData(isVoiceMessage = true), - compressBeforeSending = false, - roomIds = emptySet(), - relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) - ) - } + room.sendService().sendMedia( + attachment = audioType.toContentAttachmentData(isVoiceMessage = true), + compressBeforeSending = false, + roomIds = emptySet(), + relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) + ) } } From b9335c60657a114104a62e7a8a59e9ccb0b28c6e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:11:44 +0200 Subject: [PATCH 0169/1068] Rename const DEFAULT_CHUNK_LENGTH_IN_SECONDS --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index bd37251dd4..3a9aac12d5 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -22,5 +22,5 @@ object VoiceBroadcastConstants { const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" /** Default voice broadcast chunk duration, in seconds. */ - const val DEFAULT_CHUNK_LENGTH = 30 + const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 30 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 81a98f6fce..2a306bcd28 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -65,7 +65,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") - val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH // Todo Get the length from the room settings + val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH_IN_SECONDS // Todo Get the length from the room settings val eventId = room.stateService().sendStateEvent( eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, From ce14270fab1eb001b8c74ee68bd003bbd790ab27 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 07:18:21 +0200 Subject: [PATCH 0170/1068] Introduce MessageAudioEvent --- .../room/model/message/MessageAudioEvent.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt new file mode 100644 index 0000000000..38ced8f385 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt @@ -0,0 +1,47 @@ +/* + * 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.model.message + +import org.matrix.android.sdk.api.extensions.tryOrNull +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 + +/** + * [Event] wrapper for [EventType.MESSAGE] event type. + * Provides additional fields and functions related to this event type. + */ +@JvmInline +value class MessageAudioEvent(val root: Event) { + + /** + * The mapped [MessageAudioContent] model of the event content. + */ + val content: MessageAudioContent + get() = root.getClearContent().toModel() as MessageAudioContent + + init { + require(tryOrNull { content } != null) + } +} + +/** + * Map a [EventType.MESSAGE] event to a [MessageAudioEvent]. + */ +fun Event.asMessageAudioEvent() = if (getClearType() == EventType.MESSAGE) { + tryOrNull { MessageAudioEvent(this) } +} else null From 5f35926ce6cdeca71cca9f3b4c3364ab040d084b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 11:21:15 +0200 Subject: [PATCH 0171/1068] Voice Broadcast - Hide related voice message events --- .../android/sdk/api/session/events/model/Event.kt | 5 +++++ .../detail/timeline/factory/MessageItemFactory.kt | 7 +++++-- .../detail/timeline/helper/TimelineEventsGroups.kt | 11 +++++++++-- 3 files changed, 19 insertions(+), 4 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 f5d2c0d9a0..71daf4cc4f 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 @@ -31,6 +31,7 @@ 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.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread import org.matrix.android.sdk.api.session.room.send.SendState @@ -357,6 +358,10 @@ fun Event.isAudioMessage(): Boolean { } } +fun Event.isVoiceMessage(): Boolean { + return this.asMessageAudioEvent()?.content?.voiceMessageIndicator != null +} + fun Event.isFileMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_FILE -> true 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 06da69fc1a..d39f933363 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 @@ -323,7 +323,10 @@ class MessageItemFactory @Inject constructor( informationData: MessageInformationData, highlight: Boolean, attributes: AbsMessageItem.Attributes - ): MessageVoiceItem { + ): MessageVoiceItem? { + val eventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } + if (eventsGroup != null && eventsGroup.getLastDisplayableEvent().eventId != params.event.eventId) return null + val fileUrl = getAudioFileUrl(messageContent, informationData) val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params) @@ -722,7 +725,7 @@ class MessageItemFactory @Inject constructor( ): MessageVoiceBroadcastItem? { if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null - val mostRecentEvent = voiceBroadcastEventsGroup.getLastEvent() + val mostRecentEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() val mostRecentMessageContent = (mostRecentEvent.getVectorLastMessageContent() as? MessageVoiceBroadcastInfoContent) ?: return null return MessageVoiceBroadcastItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index 13de456e84..ccfdac8f15 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -22,6 +22,9 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.isVoiceMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -61,6 +64,10 @@ class TimelineEventsGroups { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey + type == EventType.MESSAGE && root.isVoiceMessage() -> { + // Group voice messages with a reference to an eventId + root.getRelationContent()?.takeIf { it.type == RelationType.REFERENCE }?.eventId + } else -> { null } @@ -134,8 +141,8 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) { } class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { - fun getLastEvent(): TimelineEvent { + fun getLastDisplayableEvent(): TimelineEvent { return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } - ?: group.events.maxBy { it.root.originServerTs ?: 0L } + ?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L } } } From 032c0152e41c856da0ca8e73df0ad8aa33e4207f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 15:45:07 +0200 Subject: [PATCH 0172/1068] Voice Broadcast - Move timeline item creation to dedicated factory --- .../timeline/factory/MessageItemFactory.kt | 26 +-------- .../factory/VoiceBroadcastItemFactory.kt | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt 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 d39f933363..8568f2f342 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 @@ -43,7 +43,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory -import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem @@ -58,8 +57,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_ -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_ import im.vector.app.features.home.room.detail.timeline.item.PollItem @@ -83,7 +80,6 @@ import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.AudioWaveformView import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -141,6 +137,7 @@ class MessageItemFactory @Inject constructor( private val urlMapProvider: UrlMapProvider, private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory, private val pollItemViewStateFactory: PollItemViewStateFactory, + private val voiceBroadcastItemFactory: VoiceBroadcastItemFactory, ) { // TODO inject this properly? @@ -203,7 +200,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, params.eventsGroup, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(messageContent, params.eventsGroup, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { @@ -716,25 +713,6 @@ class MessageItemFactory @Inject constructor( .highlighted(highlight) } - private fun buildVoiceBroadcastItem( - messageContent: MessageVoiceBroadcastInfoContent, - eventsGroup: TimelineEventsGroup?, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastItem? { - if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null - val mostRecentEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() - val mostRecentMessageContent = (mostRecentEvent.getVectorLastMessageContent() as? MessageVoiceBroadcastInfoContent) ?: return null - return MessageVoiceBroadcastItem_() - .attributes(attributes) - .highlighted(highlight) - .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) - .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) - } - private fun List?.toFft(): List? { return this ?.filterNotNull() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt new file mode 100644 index 0000000000..aae2f67631 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -0,0 +1,53 @@ +/* + * Copyright 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.home.room.detail.timeline.factory + +import im.vector.app.core.extensions.getVectorLastMessageContent +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup +import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup +import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import javax.inject.Inject + +class VoiceBroadcastItemFactory @Inject constructor( + private val avatarSizeProvider: AvatarSizeProvider, +) { + + fun create( + messageContent: MessageVoiceBroadcastInfoContent, + eventsGroup: TimelineEventsGroup?, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageVoiceBroadcastItem? { + // Only display item of the initial event with updated data + if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null + val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() + val mostRecentMessageContent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent()?.content ?: return null + return MessageVoiceBroadcastItem_() + .attributes(attributes) + .highlighted(highlight) + .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) + } +} From eb44b022288e2a897d87768db59f52a374c35af0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 06:49:01 +0200 Subject: [PATCH 0173/1068] Create VoiceBroadcastMediaPlayer --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt new file mode 100644 index 0000000000..7e30347d18 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -0,0 +1,163 @@ +/* + * 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.voicebroadcast + +import android.media.AudioAttributes +import android.media.MediaPlayer +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State +import im.vector.app.features.voice.VoiceFailure +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class VoiceBroadcastPlayer @Inject constructor( + private val session: Session, + private val playbackTracker: AudioMessagePlaybackTracker, +) { + + private val mediaPlayerScope = CoroutineScope(Dispatchers.IO) + + private var currentMediaPlayer: MediaPlayer? = null + private var currentPlayingIndex: Int = -1 + private var playlist = emptyList() + private val currentVoiceBroadcastEventId + get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId + + private val mediaPlayerListener = MediaPlayerListener() + + fun play(roomId: String, eventId: String) { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + when { + currentVoiceBroadcastEventId != eventId -> { + stop() + updatePlaylist(room, eventId) + startPlayback() + } + playbackTracker.getPlaybackState(eventId) is State.Playing -> pause() + else -> resumePlayback() + } + } + + fun pause() { + currentMediaPlayer?.pause() + currentVoiceBroadcastEventId?.let { playbackTracker.pausePlayback(it) } + } + + fun stop() { + currentMediaPlayer?.stop() + currentMediaPlayer?.release() + currentMediaPlayer?.setOnInfoListener(null) + currentMediaPlayer = null + currentVoiceBroadcastEventId?.let { playbackTracker.stopPlayback(it) } + playlist = emptyList() + currentPlayingIndex = -1 + } + + @Suppress("UNUSED_PARAMETER") + private fun updatePlaylist(room: Room, eventId: String) { + // TODO get the list of voice messages + } + + private fun startPlayback() { + val content = playlist.firstOrNull()?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } + mediaPlayerScope.launch { + try { + currentMediaPlayer = prepareMediaPlayer(content) + currentMediaPlayer?.start() + currentPlayingIndex = 0 + currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + prepareNextFile() + } catch (failure: Throwable) { + Timber.e(failure, "Unable to start playback") + throw VoiceFailure.UnableToPlay(failure) + } + } + } + + private fun resumePlayback() { + currentMediaPlayer?.start() + currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + } + + private suspend fun prepareNextFile() { + val nextContent = playlist.getOrNull(currentPlayingIndex + 1)?.content + if (nextContent == null) { + currentMediaPlayer?.setOnCompletionListener(mediaPlayerListener) + } else { + val nextMediaPlayer = prepareMediaPlayer(nextContent) + currentMediaPlayer?.setNextMediaPlayer(nextMediaPlayer) + } + } + + private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { + val audioFile = session.fileService().downloadFile(messageAudioContent) + return audioFile.inputStream().use { fis -> + MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder() + // Do not use CONTENT_TYPE_SPEECH / USAGE_VOICE_COMMUNICATION because we want to play loud here + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ) + setDataSource(fis.fd) + setOnInfoListener(mediaPlayerListener) + setOnErrorListener(mediaPlayerListener) + prepare() + } + } + } + + inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { + + override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { + when (what) { + MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { + currentMediaPlayer = mp + currentPlayingIndex++ + mediaPlayerScope.launch { prepareNextFile() } + } + } + return false + } + + override fun onCompletion(mp: MediaPlayer) { + // Verify that a new media has not been set in the mean time + if (!currentMediaPlayer?.isPlaying.orFalse()) { + stop() + } + } + + override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { + stop() + return true + } + } +} From 2760781f0af6e766beb52fc9ce5c78e8fd4087bc Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 22:53:16 +0200 Subject: [PATCH 0174/1068] Voice Broadcast - Introduce listening actions --- .../home/room/detail/RoomDetailAction.kt | 16 ++++++++++++---- .../home/room/detail/TimelineViewModel.kt | 11 +++++++---- .../detail/composer/MessageComposerFragment.kt | 4 ++-- .../voicebroadcast/VoiceBroadcastHelper.kt | 9 ++++++++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 3e828f62b7..f773671694 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -121,9 +121,17 @@ sealed class RoomDetailAction : VectorViewModelAction { object OpenElementCallWidget : RoomDetailAction() sealed class VoiceBroadcastAction : RoomDetailAction() { - object Start : VoiceBroadcastAction() - object Pause : VoiceBroadcastAction() - object Resume : VoiceBroadcastAction() - object Stop : VoiceBroadcastAction() + sealed class Recording : VoiceBroadcastAction() { + object Start : Recording() + object Pause : Recording() + object Resume : Recording() + object Stop : Recording() + } + + sealed class Listening : VoiceBroadcastAction() { + data class PlayOrResume(val eventId: String) : Listening() + object Pause : Listening() + object Stop : Listening() + } } } 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 511fd597fe..82ad96d645 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 @@ -604,10 +604,13 @@ class TimelineViewModel @AssistedInject constructor( if (room == null) return viewModelScope.launch { when (action) { - RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) - RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) - RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) - RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Recording.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) + is RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.eventId) + RoomDetailAction.VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback() + RoomDetailAction.VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback() } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 4721b81571..9954470e48 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -234,7 +234,7 @@ class MessageComposerFragment : VectorBaseFragment(), A } // TODO remove this when there will be a recording indicator outside of the timeline // Pause voice broadcast if the timeline is not shown anymore - it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Pause) + it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) else -> { messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) } @@ -684,7 +684,7 @@ class MessageComposerFragment : VectorBaseFragment(), A locationOwnerId = session.myUserId ) } - AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start) + AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index f682cd2f5e..b967afa9cb 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. @@ -30,6 +30,7 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, + private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -38,4 +39,10 @@ class VoiceBroadcastHelper @Inject constructor( suspend fun resumeVoiceBroadcast(roomId: String) = resumeVoiceBroadcastUseCase.execute(roomId) suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId) + + fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.play(roomId, eventId) + + fun pausePlayback() = voiceBroadcastPlayer.pause() + + fun stopPlayback() = voiceBroadcastPlayer.stop() } From 215128c213e94d3e8a9e85271d26df133f1753cf Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 17 Oct 2022 22:58:12 +0200 Subject: [PATCH 0175/1068] Voice Broadcast - Add timeline item listening state --- .../factory/VoiceBroadcastItemFactory.kt | 12 +++++- .../item/MessageVoiceBroadcastItem.kt | 42 ++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index aae2f67631..f2dfb020a1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -15,8 +15,8 @@ */ package im.vector.app.features.home.room.detail.timeline.factory -import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup @@ -25,10 +25,14 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.session.Session import javax.inject.Inject class VoiceBroadcastItemFactory @Inject constructor( + private val session: Session, private val avatarSizeProvider: AvatarSizeProvider, + private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, ) { fun create( @@ -42,11 +46,15 @@ class VoiceBroadcastItemFactory @Inject constructor( if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() - val mostRecentMessageContent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent()?.content ?: return null + val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() + val mostRecentMessageContent = mostRecentEvent?.content ?: return null + val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId return MessageVoiceBroadcastItem_() .attributes(attributes) .highlighted(highlight) .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) + .recording(isRecording) + .audioMessagePlaybackTracker(audioMessagePlaybackTracker) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt index 14a4fc6b07..1927024a36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt @@ -22,8 +22,10 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @EpoxyModelClass @@ -35,6 +37,15 @@ abstract class MessageVoiceBroadcastItem : AbsMessageItem Date: Mon, 17 Oct 2022 23:14:42 +0200 Subject: [PATCH 0176/1068] Voice Broadcast - Get voice messages events related to a given VB --- .../api/session/room/timeline/TimelineService.kt | 5 +++++ .../room/timeline/DefaultTimelineService.kt | 4 ++++ .../room/timeline/TimelineEventDataSource.kt | 15 +++++++++++++++ .../voicebroadcast/VoiceBroadcastPlayer.kt | 7 +++++-- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index 46433f387d..aa9afd5c8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -55,4 +55,9 @@ interface TimelineService { * Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO. */ fun getAttachmentMessages(): List + + /** + * Returns a snapshot list of TimelineEvent with a content relation of the given type to the given eventId. + */ + fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List } 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 53c0253876..b1a3d51b36 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 @@ -96,4 +96,8 @@ internal class DefaultTimelineService @AssistedInject constructor( override fun getAttachmentMessages(): List { return timelineEventDataSource.getAttachmentMessages(roomId) } + + override fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List { + return timelineEventDataSource.getTimelineEventsRelatedTo(roomId, relationType, eventId) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt index b1b9e4bb22..20094e4be8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -63,4 +64,18 @@ internal class TimelineEventDataSource @Inject constructor( .orEmpty() } } + + fun getTimelineEventsRelatedTo(roomId: String, eventType: String, eventId: String): List { + // TODO Remove this trick and call relations API + // see https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1roomsroomidrelationseventidreltypeeventtype + return realmSessionProvider.withRealm { realm -> + TimelineEventEntity.whereRoomId(realm, roomId) + .sort(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, Sort.ASCENDING) + .distinct(TimelineEventEntityFields.EVENT_ID) + .findAll() + .mapNotNull { + timelineEventMapper.map(it).takeIf { it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null } + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 7e30347d18..8970a65990 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -26,11 +26,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -80,9 +82,10 @@ class VoiceBroadcastPlayer @Inject constructor( currentPlayingIndex = -1 } - @Suppress("UNUSED_PARAMETER") private fun updatePlaylist(room: Room, eventId: String) { - // TODO get the list of voice messages + val timelineEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) + val audioEvents = timelineEvents.mapNotNull { it.root.asMessageAudioEvent() } + playlist = audioEvents.sortedBy { it.root.originServerTs } } private fun startPlayback() { From 402224721b2457547b41e1d6f2ad625b3fb8a33a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 00:37:12 +0200 Subject: [PATCH 0177/1068] Voice Broadcast - Add voice message extensions --- .../timeline/factory/MessageItemFactory.kt | 7 +++-- .../timeline/helper/TimelineEventsGroups.kt | 10 +++---- .../VoiceBroadcastExtensions.kt | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt 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 8568f2f342..cb947a67ce 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 @@ -44,7 +44,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_ @@ -79,6 +78,7 @@ 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.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span @@ -103,6 +103,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.settings.LightweightSettingsStorage @@ -321,8 +322,8 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, attributes: AbsMessageItem.Attributes ): MessageVoiceItem? { - val eventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } - if (eventsGroup != null && eventsGroup.getLastDisplayableEvent().eventId != params.event.eventId) return null + // Do not display voice broadcast messages + if (params.event.root.asMessageAudioEvent().isVoiceBroadcast()) return null val fileUrl = getAudioFileUrl(messageContent, informationData) val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index ccfdac8f15..d8817c1f44 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -18,15 +18,15 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.TextUtils import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId +import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.getRelationContent -import org.matrix.android.sdk.api.session.events.model.isVoiceMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.threeten.bp.Duration @@ -64,9 +64,9 @@ class TimelineEventsGroups { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey - type == EventType.MESSAGE && root.isVoiceMessage() -> { + type == EventType.MESSAGE && root.asMessageAudioEvent().isVoiceBroadcast() -> { // Group voice messages with a reference to an eventId - root.getRelationContent()?.takeIf { it.type == RelationType.REFERENCE }?.eventId + root.asMessageAudioEvent()?.getVoiceBroadcastEventId() } else -> { null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt new file mode 100644 index 0000000000..d016703968 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt @@ -0,0 +1,29 @@ +/* + * 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.voicebroadcast + +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent + +fun MessageAudioEvent?.isVoiceBroadcast() = this?.getVoiceBroadcastEventId() != null + +fun MessageAudioEvent.getVoiceBroadcastEventId(): String? = + // TODO Improve this condition by checking the referenced event type + root.takeIf { content.voiceMessageIndicator != null } + ?.getRelationContent()?.takeIf { it.type == RelationType.REFERENCE } + ?.eventId From 4a32ccecfa26303f218167ed38edd3dc43380b57 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 00:37:38 +0200 Subject: [PATCH 0178/1068] Voice Broadcast Player - Add missing try catch --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 8970a65990..41ed1ff84d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -120,7 +120,14 @@ class VoiceBroadcastPlayer @Inject constructor( } private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { - val audioFile = session.fileService().downloadFile(messageAudioContent) + // Download can fail + val audioFile = try { + session.fileService().downloadFile(messageAudioContent) + } catch (failure: Throwable) { + Timber.e(failure, "Unable to start playback") + throw VoiceFailure.UnableToPlay(failure) + } + return audioFile.inputStream().use { fis -> MediaPlayer().apply { setAudioAttributes( From e75ddf763beade39d5f77a5611840dc725726f59 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 00:50:42 +0200 Subject: [PATCH 0179/1068] Add changelog file --- changelog.d/7387.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7387.wip diff --git a/changelog.d/7387.wip b/changelog.d/7387.wip new file mode 100644 index 0000000000..881608829d --- /dev/null +++ b/changelog.d/7387.wip @@ -0,0 +1 @@ +[Voice Broadcast] Start listening to a voice broadcast From d53ad4328c5d0c356a53548398430e1b90d911aa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 01:00:50 +0200 Subject: [PATCH 0180/1068] Voice Broadcast - Pause listening outside of the timeline --- .../home/room/detail/composer/MessageComposerFragment.kt | 1 + .../home/room/detail/composer/MessageComposerViewModel.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 9954470e48..59f9737542 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -236,6 +236,7 @@ class MessageComposerFragment : VectorBaseFragment(), A // Pause voice broadcast if the timeline is not shown anymore it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) else -> { + timelineViewModel.handle(VoiceBroadcastAction.Listening.Pause) messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) } } 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 eef06d11b7..1a9f9e6291 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 @@ -42,6 +42,7 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -84,6 +85,7 @@ class MessageComposerViewModel @AssistedInject constructor( private val rainbowGenerator: RainbowGenerator, private val audioMessageHelper: AudioMessageHelper, private val analyticsTracker: AnalyticsTracker, + private val voiceBroadcastHelper: VoiceBroadcastHelper, ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -981,6 +983,8 @@ 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 = audioMessageHelper.stopAllVoiceActions(deleteRecord = false) + // TODO remove this when there will be a listening indicator outside of the timeline + voiceBroadcastHelper.pausePlayback() val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording } if (isVoiceRecording) { From 67be8c3c40f06a88345436abaf129ce3c4e52344 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 18 Oct 2022 12:44:05 +0100 Subject: [PATCH 0181/1068] The one that got away --- .../android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index 4bbf34280c..e844143889 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.rendezvous.channels import android.util.Base64 -import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock From 0c52a7ed040c5637d81c408e2ec41197de4b2563 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 18 Oct 2022 15:45:39 +0300 Subject: [PATCH 0182/1068] Fix layout after try again button is clicked. --- .../im/vector/app/features/login/qr/QrCodeLoginActivity.kt | 3 ++- .../im/vector/app/features/login/qr/QrCodeLoginViewModel.kt | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt index c9b8ae0080..a0c113224d 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.features.home.HomeActivity import im.vector.lib.core.utils.compat.getParcelableCompat @@ -66,7 +67,7 @@ class QrCodeLoginActivity : SimpleFragmentActivity() { } private fun showInstructionsFragment(qrCodeLoginArgs: QrCodeLoginArgs) { - addFragment( + replaceFragment( views.container, QrCodeLoginInstructionsFragment::class.java, qrCodeLoginArgs, diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt index 9bf3e955d3..97cca9d791 100644 --- a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt @@ -59,6 +59,11 @@ class QrCodeLoginViewModel @AssistedInject constructor( } private fun handleTryAgain() { + setState { + copy( + connectionStatus = null + ) + } _viewEvents.post(QrCodeLoginViewEvents.NavigateToInitialScreen) } From 03ac0f1f0324db27f02d83f27d97f76b1dfc397d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 11:00:21 +0200 Subject: [PATCH 0183/1068] Move Voice Broadcast feature flag to labs settings --- library/ui-strings/src/main/res/values/strings.xml | 2 ++ .../features/debug/features/DebugFeaturesStateFactory.kt | 5 ----- .../app/features/debug/features/DebugVectorFeatures.kt | 4 ---- vector-config/src/main/res/values/config-settings.xml | 2 ++ .../src/main/java/im/vector/app/features/VectorFeatures.kt | 2 -- .../home/room/detail/composer/MessageComposerFragment.kt | 2 +- .../im/vector/app/features/settings/VectorPreferences.kt | 5 +++++ vector/src/main/res/xml/vector_settings_labs.xml | 7 +++++++ 8 files changed, 17 insertions(+), 12 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 74ec175d17..1fda57180d 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3346,6 +3346,8 @@ Have greater visibility and control over all your sessions. Enable client info recording Record the client name, version, and url to recognise sessions more easily in session manager. + Enable Voice broadcast (under active development) + Be able to record and send voice broadcast in room timeline. Only ended voice broadcast can be listened (listen live broadcast is not correctly supported for the moment) %s\nis looking a little empty. diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 16e26ff3b5..0ce4416add 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -100,11 +100,6 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.reciprocateQrCodeLogin, factory = VectorFeatures::isReciprocateQrCodeLogin ), - createBooleanFeature( - label = "Enable Voice Broadcast", - key = DebugFeatureKeys.voiceBroadcastEnabled, - factory = VectorFeatures::isVoiceBroadcastEnabled - ), ) ) } diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 5c497c24ec..487094b238 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -85,9 +85,6 @@ class DebugVectorFeatures( override fun isReciprocateQrCodeLogin() = read(DebugFeatureKeys.reciprocateQrCodeLogin) ?: vectorFeatures.isReciprocateQrCodeLogin() - override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled) - ?: vectorFeatures.isVoiceBroadcastEnabled() - fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -150,5 +147,4 @@ object DebugFeatureKeys { val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled") val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers") val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login") - val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled") } diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index 7b7aac8156..504c587b8d 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -49,6 +49,8 @@ false true false + true + false diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 255ac6d188..59bccc25fc 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -43,7 +43,6 @@ interface VectorFeatures { fun isQrCodeLoginEnabled(): Boolean fun isQrCodeLoginForAllServers(): Boolean fun isReciprocateQrCodeLogin(): Boolean - fun isVoiceBroadcastEnabled(): Boolean } class DefaultVectorFeatures : VectorFeatures { @@ -62,5 +61,4 @@ class DefaultVectorFeatures : VectorFeatures { override fun isQrCodeLoginEnabled(): Boolean = true override fun isQrCodeLoginForAllServers(): Boolean = false override fun isReciprocateQrCodeLogin(): Boolean = false - override fun isVoiceBroadcastEnabled(): Boolean = false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 59f9737542..55ec922a57 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -310,7 +310,7 @@ class MessageComposerFragment : VectorBaseFragment(), A ) attachmentTypeSelector.setAttachmentVisibility( AttachmentTypeSelectorView.Type.VOICE_BROADCAST, - vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission + vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission ) } attachmentTypeSelector.show(composer.attachmentButton) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 89fcda142a..5d0f981314 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -74,6 +74,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_LABS_RICH_TEXT_EDITOR_KEY = "SETTINGS_LABS_RICH_TEXT_EDITOR_KEY" const val SETTINGS_LABS_NEW_SESSION_MANAGER_KEY = "SETTINGS_LABS_NEW_SESSION_MANAGER_KEY" const val SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY = "SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY" + const val SETTINGS_LABS_VOICE_BROADCAST_KEY = "SETTINGS_LABS_VOICE_BROADCAST_KEY" const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY" @@ -1203,4 +1204,8 @@ class VectorPreferences @Inject constructor( fun isRichTextEditorEnabled(): Boolean { return defaultPrefs.getBoolean(SETTINGS_LABS_RICH_TEXT_EDITOR_KEY, getDefault(R.bool.settings_labs_rich_text_editor_default)) } + + fun isVoiceBroadcastEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default)) + } } diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 5b519bdd91..15a255753a 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -117,4 +117,11 @@ android:title="@string/labs_enable_client_info_recording_title" app:isPreferenceVisible="@bool/settings_labs_client_info_recording_visible" /> + + From 050dff6548abfb4c9c3d38c6daa6074ed1d4f985 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 11:05:24 +0200 Subject: [PATCH 0184/1068] Voice Broadcast - Rename voice message files with sequence number --- .../features/voicebroadcast/VoiceBroadcastRecorder.kt | 3 ++- .../features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 6 +++++- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 9 +++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 2668501a8d..37ff920c57 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast +import androidx.annotation.IntRange import im.vector.app.features.voice.VoiceRecorder import java.io.File @@ -26,6 +27,6 @@ interface VoiceBroadcastRecorder : VoiceRecorder { fun startRecord(roomId: String, chunkLength: Int) fun interface Listener { - fun onVoiceMessageCreated(file: File) + fun onVoiceMessageCreated(file: File, @IntRange(from = 1) sequence: Int) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 620db721c9..404b112574 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -29,6 +29,7 @@ class VoiceBroadcastRecorderQ( ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { private var maxFileSize = 0L // zero or negative for no limit + private var currentSequence = 0 override var listener: VoiceBroadcastRecorder.Listener? = null @@ -51,6 +52,7 @@ class VoiceBroadcastRecorderQ( override fun startRecord(roomId: String, chunkLength: Int) { maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() + currentSequence = 1 startRecord(roomId) } @@ -58,6 +60,7 @@ class VoiceBroadcastRecorderQ( super.stopRecord() notifyOutputFileCreated() listener = null + currentSequence = 0 } override fun release() { @@ -71,11 +74,12 @@ class VoiceBroadcastRecorderQ( private fun onNextOutputFileStarted() { notifyOutputFileCreated() + currentSequence++ } private fun notifyOutputFileCreated() { outputFile?.let { - listener?.onVoiceMessageCreated(it) + listener?.onVoiceMessageCreated(it, currentSequence) outputFile = nextOutputFile nextOutputFile = null } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 2a306bcd28..780150d5e7 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -79,20 +79,21 @@ class StartVoiceBroadcastUseCase @Inject constructor( } private fun startRecording(room: Room, eventId: String, chunkLength: Int) { - voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> - sendVoiceFile(room, file, eventId) + voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file, sequence -> + sendVoiceFile(room, file, eventId, sequence) } voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength) } - private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String) { + private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String, sequence: Int) { val outputFileUri = FileProvider.getUriForFile( context, buildMeta.applicationId + ".fileProvider", voiceMessageFile, - "Voice message.${voiceMessageFile.extension}" + "Voice Broadcast Part ($sequence).${voiceMessageFile.extension}" ) val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return + // TODO put sequence in event content room.sendService().sendMedia( attachment = audioType.toContentAttachmentData(isVoiceMessage = true), compressBeforeSending = false, From 036511400e93af8b217c889609588b9350b3879e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 11:12:26 +0200 Subject: [PATCH 0185/1068] Voice Broadcast - Hide labs setting in Android < 10 --- .../src/main/res/values-v29/config-settings.xml | 13 +++++++++++++ .../src/main/res/values/config-settings.xml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100755 vector-config/src/main/res/values-v29/config-settings.xml diff --git a/vector-config/src/main/res/values-v29/config-settings.xml b/vector-config/src/main/res/values-v29/config-settings.xml new file mode 100755 index 0000000000..051e6e9e81 --- /dev/null +++ b/vector-config/src/main/res/values-v29/config-settings.xml @@ -0,0 +1,13 @@ + + + + + + + true + + diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index 504c587b8d..f2169e7e62 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -49,7 +49,7 @@ false true false - true + false false From ecc22a1401051f063b8c5ff54a7d4cb2d1d0f654 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 11:23:01 +0200 Subject: [PATCH 0186/1068] Voice Broadcast - Change default chunk duration --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 3a9aac12d5..d445dfdfbe 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -22,5 +22,5 @@ object VoiceBroadcastConstants { const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" /** Default voice broadcast chunk duration, in seconds. */ - const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 30 + const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 120 } From a03be5d02fface951a4da4cc51bed25e19c26ec2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 11:29:00 +0200 Subject: [PATCH 0187/1068] Add changelog --- changelog.d/7393.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7393.wip diff --git a/changelog.d/7393.wip b/changelog.d/7393.wip new file mode 100644 index 0000000000..b2112be5e9 --- /dev/null +++ b/changelog.d/7393.wip @@ -0,0 +1 @@ +[Voice Broadcast] Move the feature flag to the labs From 5a749bdccf262aac15485977b8973636af408e2d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 20:49:26 +0200 Subject: [PATCH 0188/1068] Voice Broadcast - Update labs description --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 1fda57180d..ba83a4553c 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3347,7 +3347,7 @@ Enable client info recording Record the client name, version, and url to recognise sessions more easily in session manager. Enable Voice broadcast (under active development) - Be able to record and send voice broadcast in room timeline. Only ended voice broadcast can be listened (listen live broadcast is not correctly supported for the moment) + Be able to record and send voice broadcast in room timeline.‡ %s\nis looking a little empty. From 63c02c6fef0faa5277273ad5280e8ecd75b43f43 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 21:02:58 +0200 Subject: [PATCH 0189/1068] Voice Broadcast - Restore feature flag and enable it by default --- .../app/features/debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../app/features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../src/main/java/im/vector/app/features/VectorFeatures.kt | 2 ++ .../im/vector/app/features/settings/VectorPreferences.kt | 3 ++- .../app/features/settings/labs/VectorSettingsLabsFragment.kt | 4 ++++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 0ce4416add..16e26ff3b5 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -100,6 +100,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.reciprocateQrCodeLogin, factory = VectorFeatures::isReciprocateQrCodeLogin ), + createBooleanFeature( + label = "Enable Voice Broadcast", + key = DebugFeatureKeys.voiceBroadcastEnabled, + factory = VectorFeatures::isVoiceBroadcastEnabled + ), ) ) } diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 487094b238..5c497c24ec 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -85,6 +85,9 @@ class DebugVectorFeatures( override fun isReciprocateQrCodeLogin() = read(DebugFeatureKeys.reciprocateQrCodeLogin) ?: vectorFeatures.isReciprocateQrCodeLogin() + override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled) + ?: vectorFeatures.isVoiceBroadcastEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -147,4 +150,5 @@ object DebugFeatureKeys { val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled") val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers") val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login") + val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 59bccc25fc..95cf272abd 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -43,6 +43,7 @@ interface VectorFeatures { fun isQrCodeLoginEnabled(): Boolean fun isQrCodeLoginForAllServers(): Boolean fun isReciprocateQrCodeLogin(): Boolean + fun isVoiceBroadcastEnabled(): Boolean } class DefaultVectorFeatures : VectorFeatures { @@ -61,4 +62,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isQrCodeLoginEnabled(): Boolean = true override fun isQrCodeLoginForAllServers(): Boolean = false override fun isReciprocateQrCodeLogin(): Boolean = false + override fun isVoiceBroadcastEnabled(): Boolean = true } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 5d0f981314..2dc8b12160 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -1206,6 +1206,7 @@ class VectorPreferences @Inject constructor( } fun isVoiceBroadcastEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default)) + return vectorFeatures.isVoiceBroadcastEnabled() && + defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default)) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt index 6c31e32567..f9a5a5f9cc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt @@ -90,6 +90,10 @@ class VectorSettingsLabsFragment : } } + findPreference(VectorPreferences.SETTINGS_LABS_VOICE_BROADCAST_KEY)?.let { pref -> + pref.isVisible = vectorFeatures.isVoiceBroadcastEnabled() + } + configureUnreadNotificationsAsTabPreference() configureEnableClientInfoRecordingPreference() } From 90803be3eec728c896b097b8194a649920d54284 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 21:06:59 +0200 Subject: [PATCH 0190/1068] Voice Broadcast - Move Android API check on fragment --- .../src/main/res/values-v29/config-settings.xml | 13 ------------- .../src/main/res/values/config-settings.xml | 2 +- .../settings/labs/VectorSettingsLabsFragment.kt | 4 +++- 3 files changed, 4 insertions(+), 15 deletions(-) delete mode 100755 vector-config/src/main/res/values-v29/config-settings.xml diff --git a/vector-config/src/main/res/values-v29/config-settings.xml b/vector-config/src/main/res/values-v29/config-settings.xml deleted file mode 100755 index 051e6e9e81..0000000000 --- a/vector-config/src/main/res/values-v29/config-settings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - true - - diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index f2169e7e62..504c587b8d 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -49,7 +49,7 @@ false true false - false + true false diff --git a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt index f9a5a5f9cc..c10411301f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.labs +import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.widget.TextView @@ -91,7 +92,8 @@ class VectorSettingsLabsFragment : } findPreference(VectorPreferences.SETTINGS_LABS_VOICE_BROADCAST_KEY)?.let { pref -> - pref.isVisible = vectorFeatures.isVoiceBroadcastEnabled() + // Voice Broadcast recording is not available on Android < 10 + pref.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && vectorFeatures.isVoiceBroadcastEnabled() } configureUnreadNotificationsAsTabPreference() From fbf242756ef384326a705c4fcdf7375ca252c7b7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 14:34:31 +0200 Subject: [PATCH 0191/1068] Allow additional content when sending an event --- .../sdk/api/session/room/send/SendService.kt | 34 ++++-- .../session/content/UploadContentWorker.kt | 8 +- .../session/room/send/DefaultSendService.kt | 36 ++++--- .../room/send/LocalEchoEventFactory.kt | 102 ++++++++++++------ 4 files changed, 122 insertions(+), 58 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 53b49129c4..6a6fadc95a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -45,18 +45,30 @@ interface SendService { * @param text the text message to send * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable + fun sendTextMessage( + text: CharSequence, + msgType: String = MessageType.MSGTYPE_TEXT, + autoMarkdown: Boolean = false, + additionalContent: Content? = null, + ): Cancelable /** * Method to send a text message with a formatted body. * @param text the text message to send * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable + fun sendFormattedTextMessage( + text: String, + formattedText: String, + msgType: String = MessageType.MSGTYPE_TEXT, + additionalContent: Content? = null, + ): Cancelable /** * Method to quote an events content. @@ -65,6 +77,7 @@ interface SendService { * @param formattedText the formatted text message to send * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @param rootThreadEventId when this param is not null, the message will be sent in this specific thread + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ fun sendQuotedTextMessage( @@ -73,6 +86,7 @@ interface SendService { formattedText: String? = null, autoMarkdown: Boolean, rootThreadEventId: String? = null, + additionalContent: Content? = null, ): Cancelable /** @@ -83,6 +97,7 @@ interface SendService { * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread * @param relatesTo add a relation content to the media event + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ fun sendMedia( @@ -91,6 +106,7 @@ interface SendService { roomIds: Set, rootThreadEventId: String? = null, relatesTo: RelationDefaultContent? = null, + additionalContent: Content? = null, ): Cancelable /** @@ -100,6 +116,7 @@ interface SendService { * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, all the Media will be sent in this specific thread + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ fun sendMedias( @@ -107,6 +124,7 @@ interface SendService { compressBeforeSending: Boolean, roomIds: Set, rootThreadEventId: String? = null, + additionalContent: Content? = null, ): Cancelable /** @@ -114,31 +132,35 @@ interface SendService { * @param pollType indicates open or closed polls * @param question the question * @param options list of options + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendPoll(pollType: PollType, question: String, options: List): Cancelable + fun sendPoll(pollType: PollType, question: String, options: List, additionalContent: Content? = null): Cancelable /** * Method to send a poll response. * @param pollEventId the poll currently replied to * @param answerId The id of the answer + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun voteToPoll(pollEventId: String, answerId: String): Cancelable + fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content? = null): Cancelable /** * End a poll in the room. * @param pollEventId event id of the poll + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun endPoll(pollEventId: String): Cancelable + fun endPoll(pollEventId: String, additionalContent: Content? = null): Cancelable /** * Redact (delete) the given event. * @param event The event to redact * @param reason Optional reason string + * @param additionalContent additional content to put in the event content */ - fun redactEvent(event: Event, reason: String?): Cancelable + fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable /** * Schedule this message to be resent. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 1e62b5d7f5..db1cd1b33b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -407,7 +408,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter newAttachmentAttributes: NewAttachmentAttributes ) { localEchoRepository.updateEcho(eventId) { _, event -> - val messageContent: MessageContent? = event.asDomain().content.toModel() + val content: Content? = event.asDomain().content + val messageContent: MessageContent? = content.toModel() + // Retrieve potential additional content from the original event + val additionalContent = content.orEmpty() - messageContent?.toContent().orEmpty().keys val updatedContent = when (messageContent) { is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes) is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes) @@ -415,7 +419,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize) else -> messageContent } - event.content = ContentMapper.map(updatedContent.toContent()) + event.content = ContentMapper.map(updatedContent.toContent().plus(additionalContent)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index aa305e6067..9cdbc7ff46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage @@ -88,14 +89,14 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable { - return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown) + override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable { - return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType) + override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } @@ -105,7 +106,8 @@ internal class DefaultSendService @AssistedInject constructor( text: String, formattedText: String?, autoMarkdown: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, + additionalContent: Content?, ): Cancelable { return localEchoEventFactory.createQuotedTextEvent( roomId = roomId, @@ -113,33 +115,34 @@ internal class DefaultSendService @AssistedInject constructor( text = text, formattedText = formattedText, autoMarkdown = autoMarkdown, - rootThreadEventId = rootThreadEventId + rootThreadEventId = rootThreadEventId, + additionalContent = additionalContent, ) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun sendPoll(pollType: PollType, question: String, options: List): Cancelable { - return localEchoEventFactory.createPollEvent(roomId, pollType, question, options) + override fun sendPoll(pollType: PollType, question: String, options: List, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun voteToPoll(pollEventId: String, answerId: String): Cancelable { - return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId) + override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun endPoll(pollEventId: String): Cancelable { - return localEchoEventFactory.createEndPollEvent(roomId, pollEventId) + override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun redactEvent(event: Event, reason: String?): Cancelable { + override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { // TODO manage media/attachements? - val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent) .also { createLocalEcho(it) } return eventSenderProcessor.postRedaction(redactionEcho, reason) } @@ -265,7 +268,8 @@ internal class DefaultSendService @AssistedInject constructor( attachments: List, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? + rootThreadEventId: String?, + additionalContent: Content?, ): Cancelable { return attachments.mapTo(CancelableBag()) { sendMedia( @@ -283,6 +287,7 @@ internal class DefaultSendService @AssistedInject constructor( roomIds: Set, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content?, ): Cancelable { // Ensure that the event will not be send in a thread if we are a different flow. // Like sending files to multiple rooms @@ -299,6 +304,7 @@ internal class DefaultSendService @AssistedInject constructor( attachment = attachment, rootThreadEventId = rootThreadId, relatesTo, + additionalContent, ).also { event -> createLocalEcho(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 1d7f624eba..7d8605c2bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -95,12 +95,12 @@ internal class LocalEchoEventFactory @Inject constructor( private val permalinkFactory: PermalinkFactory, private val clock: Clock, ) { - fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event { + fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean, additionalContent: Content? = null): Event { if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) { - return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType) + return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType, additionalContent) } val content = MessageTextContent(msgType = msgType, body = text.toString()) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent { @@ -116,8 +116,8 @@ internal class LocalEchoEventFactory @Inject constructor( return TextContent(text.toString()) } - fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event { - return createMessageEvent(roomId, textContent.toMessageTextContent(msgType)) + fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String, additionalContent: Content? = null): Event { + return createMessageEvent(roomId, textContent.toMessageTextContent(msgType), additionalContent) } fun createReplaceTextEvent( @@ -128,6 +128,7 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String, + additionalContent: Content? = null, ): Event { val content = if (newBodyFormattedText != null) { TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType) @@ -141,7 +142,8 @@ internal class LocalEchoEventFactory @Inject constructor( body = compatibilityText, relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), newContent = content, - ) + ), + additionalContent, ) } @@ -167,6 +169,7 @@ internal class LocalEchoEventFactory @Inject constructor( targetEventId: String, question: String, options: List, + additionalContent: Content? = null, ): Event { val newContent = MessagePollContent( relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), @@ -179,7 +182,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.POLL_START.first(), - content = newContent.toContent() + content = newContent.toContent().plus(additionalContent.orEmpty()) ) } @@ -187,6 +190,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, pollEventId: String, answerId: String, + additionalContent: Content? = null, ): Event { val content = MessagePollResponseContent( body = answerId, @@ -203,7 +207,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.POLL_RESPONSE.first(), - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -213,6 +217,7 @@ internal class LocalEchoEventFactory @Inject constructor( pollType: PollType, question: String, options: List, + additionalContent: Content? = null, ): Event { val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() @@ -222,7 +227,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.POLL_START.first(), - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -230,6 +235,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createEndPollEvent( roomId: String, eventId: String, + additionalContent: Content? = null, ): Event { val content = MessageEndPollContent( relatesTo = RelationDefaultContent( @@ -244,7 +250,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.POLL_END.first(), - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -255,6 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor( longitude: Double, uncertainty: Double?, isUserLocation: Boolean, + additionalContent: Content? = null, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN @@ -266,7 +273,7 @@ internal class LocalEchoEventFactory @Inject constructor( unstableTimestampMillis = clock.epochMillis(), unstableText = geoUri ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } fun createLiveLocationEvent( @@ -275,6 +282,7 @@ internal class LocalEchoEventFactory @Inject constructor( latitude: Double, longitude: Double, uncertainty: Double?, + additionalContent: Content? = null, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val content = MessageBeaconLocationDataContent( @@ -293,7 +301,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.BEACON_LOCATION_DATA.first(), - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -306,6 +314,7 @@ internal class LocalEchoEventFactory @Inject constructor( autoMarkdown: Boolean, msgType: String, compatibilityText: String, + additionalContent: Content? = null, ): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" @@ -340,7 +349,8 @@ internal class LocalEchoEventFactory @Inject constructor( formattedBody = replyFormatted ) .toContent() - ) + ), + additionalContent, ) } @@ -349,23 +359,32 @@ internal class LocalEchoEventFactory @Inject constructor( attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content? = null, ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) + ContentAttachmentData.Type.AUDIO -> createAudioEvent( + roomId, + attachment, + isVoiceMessage = false, + rootThreadEventId = rootThreadEventId, + relatesTo, + additionalContent + ) ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent( roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId, relatesTo, + additionalContent, ) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) } } - fun createReactionEvent(roomId: String, targetEventId: String, reaction: String): Event { + fun createReactionEvent(roomId: String, targetEventId: String, reaction: String, additionalContent: Content? = null): Event { val content = ReactionContent( ReactionInfo( RelationType.ANNOTATION, @@ -380,7 +399,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.REACTION, - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -390,6 +409,7 @@ internal class LocalEchoEventFactory @Inject constructor( attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content?, ): Event { var width = attachment.width var height = attachment.height @@ -417,7 +437,7 @@ internal class LocalEchoEventFactory @Inject constructor( url = attachment.queryUri.toString(), relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createVideoEvent( @@ -425,6 +445,7 @@ internal class LocalEchoEventFactory @Inject constructor( attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content?, ): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -459,7 +480,7 @@ internal class LocalEchoEventFactory @Inject constructor( url = attachment.queryUri.toString(), relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createAudioEvent( @@ -468,6 +489,7 @@ internal class LocalEchoEventFactory @Inject constructor( isVoiceMessage: Boolean, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content? ): Event { val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -485,7 +507,7 @@ internal class LocalEchoEventFactory @Inject constructor( voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createFileEvent( @@ -493,6 +515,7 @@ internal class LocalEchoEventFactory @Inject constructor( attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?, + additionalContent: Content? ): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, @@ -504,15 +527,16 @@ internal class LocalEchoEventFactory @Inject constructor( url = attachment.queryUri.toString(), relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } - private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event { - return createEvent(roomId, EventType.MESSAGE, content.toContent()) + private fun createMessageEvent(roomId: String, content: MessageContent, additionalContent: Content?): Event { + return createEvent(roomId, EventType.MESSAGE, content.toContent(), additionalContent) } - fun createEvent(roomId: String, type: String, content: Content?): Event { + fun createEvent(roomId: String, type: String, content: Content?, additionalContent: Content? = null): Event { val newContent = enhanceStickerIfNeeded(type, content) ?: content + val updatedNewContent = newContent?.plus(additionalContent.orEmpty()) ?: additionalContent val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -520,7 +544,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = type, - content = newContent, + content = updatedNewContent, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -555,6 +579,7 @@ internal class LocalEchoEventFactory @Inject constructor( msgType: String, autoMarkdown: Boolean, formattedText: String?, + additionalContent: Content? = null, ): Event { val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown) return createEvent( @@ -564,8 +589,7 @@ internal class LocalEchoEventFactory @Inject constructor( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), msgType = msgType - ) - .toContent() + ).toContent().plus(additionalContent.orEmpty()) ) } @@ -584,6 +608,7 @@ internal class LocalEchoEventFactory @Inject constructor( autoMarkdown: Boolean, rootThreadEventId: String? = null, showInThread: Boolean, + additionalContent: Content? = null ): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null @@ -621,7 +646,7 @@ internal class LocalEchoEventFactory @Inject constructor( showInThread = showInThread ) ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun generateThreadRelationContent(rootThreadEventId: String) = @@ -750,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor( } } */ - fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event { + fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event { val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -759,7 +784,7 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REDACTION, redacts = eventId, - content = reason?.let { mapOf("reason" to it).toContent() }, + content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -776,9 +801,14 @@ internal class LocalEchoEventFactory @Inject constructor( formattedText: String?, autoMarkdown: Boolean, rootThreadEventId: String?, + additionalContent: Content? = null, ): Event { val messageContent = quotedEvent.getLastMessageContent() - val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body } + val textMsg = if (messageContent is MessageContentWithFormattedBody) { + messageContent.formattedBody + } else { + messageContent?.body + } val quoteText = legacyRiotQuoteText(textMsg, text) val quoteFormattedText = "
$textMsg
$formattedText" @@ -791,13 +821,15 @@ internal class LocalEchoEventFactory @Inject constructor( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), msgType = MessageType.MSGTYPE_TEXT - ) + ), + additionalContent, ) } else { createFormattedTextEvent( roomId, markdownParser.parse(quoteText, force = true, advanced = autoMarkdown).copy(formattedText = quoteFormattedText), - MessageType.MSGTYPE_TEXT + MessageType.MSGTYPE_TEXT, + additionalContent, ) } } From 1647fe233f5211457d6f5d872fbd40cc33def2ff Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 15:45:44 +0200 Subject: [PATCH 0192/1068] Voice Broadcast - Introduce io.element.voice_broadcast_chunk key in voice messages --- .../voicebroadcast/VoiceBroadcastConstants.kt | 7 +++++- .../VoiceBroadcastExtensions.kt | 18 +++++++------ .../voicebroadcast/VoiceBroadcastPlayer.kt | 2 +- .../model/VoiceBroadcastChunk.kt | 25 +++++++++++++++++++ .../usecase/StartVoiceBroadcastUseCase.kt | 6 ++++- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastChunk.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index d445dfdfbe..7b937f740e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -16,11 +16,16 @@ package im.vector.app.features.voicebroadcast +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent + object VoiceBroadcastConstants { /** Voice Broadcast State Event. */ const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" + /** Custom key passed to the [MessageAudioContent] with Voice Broadcast information. */ + const val VOICE_BROADCAST_CHUNK_KEY = "io.element.voice_broadcast_chunk" + /** Default voice broadcast chunk duration, in seconds. */ - const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 120 + const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 10 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt index d016703968..fe449c679e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt @@ -16,14 +16,18 @@ package im.vector.app.features.voicebroadcast -import org.matrix.android.sdk.api.session.events.model.RelationType +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent -fun MessageAudioEvent?.isVoiceBroadcast() = this?.getVoiceBroadcastEventId() != null +fun MessageAudioEvent?.isVoiceBroadcast() = this?.root?.getClearContent()?.get(VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY) != null +fun MessageAudioEvent.getVoiceBroadcastEventId(): String? = if (isVoiceBroadcast()) root.getRelationContent()?.eventId else null -fun MessageAudioEvent.getVoiceBroadcastEventId(): String? = - // TODO Improve this condition by checking the referenced event type - root.takeIf { content.voiceMessageIndicator != null } - ?.getRelationContent()?.takeIf { it.type == RelationType.REFERENCE } - ?.eventId +fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? { + @Suppress("UNCHECKED_CAST") + return (root.getClearContent()?.get(VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY) as? Content).toModel() +} + +val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 41ed1ff84d..dfd50ea5cb 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -85,7 +85,7 @@ class VoiceBroadcastPlayer @Inject constructor( private fun updatePlaylist(room: Room, eventId: String) { val timelineEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) val audioEvents = timelineEvents.mapNotNull { it.root.asMessageAudioEvent() } - playlist = audioEvents.sortedBy { it.root.originServerTs } + playlist = audioEvents.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs } } private fun startPlayback() { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastChunk.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastChunk.kt new file mode 100644 index 0000000000..e0f6e6e7b1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastChunk.kt @@ -0,0 +1,25 @@ +/* + * 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.voicebroadcast.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VoiceBroadcastChunk( + @Json(name = "sequence") val sequence: Int? = null +) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 780150d5e7..ba1799c520 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -23,6 +23,7 @@ import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.multipicker.utils.toMultiPickerAudioType @@ -98,7 +99,10 @@ class StartVoiceBroadcastUseCase @Inject constructor( attachment = audioType.toContentAttachmentData(isVoiceMessage = true), compressBeforeSending = false, roomIds = emptySet(), - relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) + relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId), + additionalContent = mapOf( + VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY to VoiceBroadcastChunk(sequence = sequence).toContent() + ) ) } } From 64456860e239529f71f944f670c4e7c4a58f87bd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 16:00:23 +0200 Subject: [PATCH 0193/1068] Voice Broadcast - Add deviceId in state event content --- .../voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt | 2 ++ .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index 5044bb5c34..a9db63c538 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -38,6 +38,8 @@ data class MessageVoiceBroadcastInfoContent( @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null, + /** The device from which the broadcast has been started. */ + @Json(name = "device_id") val deviceId: String? = null, /** The [VoiceBroadcastState] value. **/ @Json(name = "state") val voiceBroadcastStateStr: String = "", /** The length of the voice chunks in seconds. **/ diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index ba1799c520..7e43439b45 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -71,6 +71,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( + deviceId = session.sessionParams.deviceId, voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, chunkLength = chunkLength, ).toContent() From 5438c7e089e724675ed6dffca5678641e934c2dd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 16:39:13 +0200 Subject: [PATCH 0194/1068] Add changelog --- changelog.d/7397.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7397.wip diff --git a/changelog.d/7397.wip b/changelog.d/7397.wip new file mode 100644 index 0000000000..1a7d1866a6 --- /dev/null +++ b/changelog.d/7397.wip @@ -0,0 +1 @@ +[Voice Broadcast] Add additional data in events From 5004db07fb49548a8166fd749f94d73a6780d526 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 17:22:44 +0200 Subject: [PATCH 0195/1068] Remove legacy comment --- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7e43439b45..7cb66cd9e5 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -95,7 +95,6 @@ class StartVoiceBroadcastUseCase @Inject constructor( "Voice Broadcast Part ($sequence).${voiceMessageFile.extension}" ) val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return - // TODO put sequence in event content room.sendService().sendMedia( attachment = audioType.toContentAttachmentData(isVoiceMessage = true), compressBeforeSending = false, From a658e7727afd556fe266d2e4d7f143b440a5ae22 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 17:39:06 +0200 Subject: [PATCH 0196/1068] Voice Broadcast - Update chunk length to 120 sec --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 7b937f740e..551eaa4dac 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -27,5 +27,5 @@ object VoiceBroadcastConstants { const val VOICE_BROADCAST_CHUNK_KEY = "io.element.voice_broadcast_chunk" /** Default voice broadcast chunk duration, in seconds. */ - const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 10 + const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 120 } From 0781ee84d96c117b8904df01aafac4a88ed2a5b1 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 17:39:55 +0200 Subject: [PATCH 0197/1068] Reformat file --- .../app/features/voicebroadcast/VoiceBroadcastExtensions.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt index fe449c679e..f9da2e76b1 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent fun MessageAudioEvent?.isVoiceBroadcast() = this?.root?.getClearContent()?.get(VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY) != null + fun MessageAudioEvent.getVoiceBroadcastEventId(): String? = if (isVoiceBroadcast()) root.getRelationContent()?.eventId else null fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? { From e4a98378a1c12886998eb6b8d3ee1304e8a3db4f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 21:11:03 +0200 Subject: [PATCH 0198/1068] Fix unit test --- .../src/test/java/im/vector/app/test/fakes/FakeSendService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSendService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSendService.kt index 04b9b95ce1..425a485561 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSendService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSendService.kt @@ -17,6 +17,7 @@ package im.vector.app.test.fakes import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.util.Cancelable @@ -25,5 +26,5 @@ class FakeSendService : SendService by mockk() { private val cancelable = mockk() - override fun sendPoll(pollType: PollType, question: String, options: List): Cancelable = cancelable + override fun sendPoll(pollType: PollType, question: String, options: List, additionalContent: Content?): Cancelable = cancelable } From 2e4034ffc105d000f1efc5976c08fdb76d9ed116 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 22:05:45 +0200 Subject: [PATCH 0199/1068] Remove extra character --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index ba83a4553c..61779036be 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3347,7 +3347,7 @@ Enable client info recording Record the client name, version, and url to recognise sessions more easily in session manager. Enable Voice broadcast (under active development) - Be able to record and send voice broadcast in room timeline.‡ + Be able to record and send voice broadcast in room timeline. %s\nis looking a little empty. From 096b423cc1ebd3774c40ae8cc9604aa50da57d24 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 22:10:18 +0200 Subject: [PATCH 0200/1068] Update changelog wording --- changelog.d/7393.wip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/7393.wip b/changelog.d/7393.wip index b2112be5e9..7d82dc5769 100644 --- a/changelog.d/7393.wip +++ b/changelog.d/7393.wip @@ -1 +1 @@ -[Voice Broadcast] Move the feature flag to the labs +[Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) From cafbb17caaa9b5b1799b88bd679829e1fca05a03 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 22:37:37 +0200 Subject: [PATCH 0201/1068] Change wording to lowercase --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 61779036be..fac36ffa52 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3346,7 +3346,7 @@ Have greater visibility and control over all your sessions. Enable client info recording Record the client name, version, and url to recognise sessions more easily in session manager. - Enable Voice broadcast (under active development) + Enable voice broadcast (under active development) Be able to record and send voice broadcast in room timeline. From d0c5c3bc43664ac35a8e2441ce2fed8bcfeb2230 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 23:07:29 +0000 Subject: [PATCH 0202/1068] Bump flipper from 0.171.0 to 0.171.1 Bumps `flipper` from 0.171.0 to 0.171.1. Updates `flipper` from 0.171.0 to 0.171.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.171.0...v0.171.1) Updates `flipper-network-plugin` from 0.171.0 to 0.171.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.171.0...v0.171.1) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 80380c2be2..f081e0a874 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -18,7 +18,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.171.0" +def flipper = "0.171.1" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From ff4ec3f5838d57356026849af5f8ff6117704a19 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 10:00:34 +0200 Subject: [PATCH 0203/1068] Fix typo --- changelog.d/7363.wip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/7363.wip b/changelog.d/7363.wip index b5a5f4c352..ee71e799fa 100644 --- a/changelog.d/7363.wip +++ b/changelog.d/7363.wip @@ -1 +1 @@ -[Voice Broadcast] Record and send not aggregated voice messages to the room +[Voice Broadcast] Record and send non aggregated voice messages to the room From 4f652f102676e8bd2364149d00009f9d0f1ea656 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 19 Oct 2022 09:12:09 +0100 Subject: [PATCH 0204/1068] Request changes from review --- .../sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt | 7 +------ .../rendezvous/transports/SimpleHttpRendezvousTransport.kt | 3 ++- .../verification/SASDefaultVerificationTransaction.kt | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt index e844143889..c1d6b1b70e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -46,11 +46,6 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu private const val ALGORITHM_SPEC = "AES/GCM/NoPadding" private const val KEY_SPEC = "AES" private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value - - // this is the same representation as for SAS but we delimit by dashes instead of spaces for readability - private fun getDecimalCodeRepresentation(byteArray: ByteArray): String { - return SASDefaultVerificationTransaction.getDecimalCodeRepresentation(byteArray).replace(" ", "-") - } } @JsonClass(generateAdapter = true) @@ -114,7 +109,7 @@ class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPu aesKey = sas.generateShortCode(aesInfo, 32) val rawChecksum = sas.generateShortCode(aesInfo, 5) - return getDecimalCodeRepresentation(rawChecksum) + return SASDefaultVerificationTransaction.getDecimalCodeRepresentation(rawChecksum, separator = "-") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt index 7a5cbe5424..620b599e3d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransport import timber.log.Timber import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale /** * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 @@ -87,7 +88,7 @@ class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTranspor val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response") response.header("expires")?.let { - val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz") + val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) expiresAt = format.parse(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt index b306288c5e..29b416bb82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -95,7 +95,7 @@ internal abstract class SASDefaultVerificationTransaction( * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers, * or with the three numbers on separate lines. */ - fun getDecimalCodeRepresentation(byteArray: ByteArray): String { + fun getDecimalCodeRepresentation(byteArray: ByteArray, separator: String = " "): String { val b0 = byteArray[0].toUnsignedInt() // need unsigned byte val b1 = byteArray[1].toUnsignedInt() // need unsigned byte val b2 = byteArray[2].toUnsignedInt() // need unsigned byte @@ -107,7 +107,7 @@ internal abstract class SASDefaultVerificationTransaction( val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 - return "$first $second $third" + return "$first$separator$second$separator$third" } } From 62d4c811ddd05c73a657628c984a37d93cfb7525 Mon Sep 17 00:00:00 2001 From: Nizami Date: Tue, 18 Oct 2022 09:37:48 +0000 Subject: [PATCH 0205/1068] Translated using Weblate (Azerbaijani) Currently translated at 2.4% (62 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/az/ --- .../src/main/res/values-az/strings.xml | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/library/ui-strings/src/main/res/values-az/strings.xml b/library/ui-strings/src/main/res/values-az/strings.xml index 84f2772950..53100db285 100644 --- a/library/ui-strings/src/main/res/values-az/strings.xml +++ b/library/ui-strings/src/main/res/values-az/strings.xml @@ -1,6 +1,5 @@ - %s-nin dəvəti %1$s dəvət etdi %2$s %1$s sizi dəvət etdi @@ -27,37 +26,22 @@ bütün otaq üzvləri. hər kəs. %s bu otağı təkmilləşdirdi. - - (avatar da dəyişdirilib) %1$s otaq adını sildi %1$s otaq mövzusunu sildi %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi %1$s %2$s üçün dəvəti qəbul etdi - ** Şifrəni aça bilmir: %s ** Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. - Mesaj göndərmək olmur - - Matris xətası - - Şifrəli mesaj - Elektron poçt ünvanı Telefon nömrəsi - Otağa dəvət - %1$s və %2$s - - - Boş otaq - İlkin sinxronizasiya: \nHesab idxal olunur… İlkin sinxronizasiya: @@ -72,9 +56,7 @@ \nTərk olunmuş otaqların idxalı İlkin sinxronizasiya: \nHesab məlumatlarının idxalı - Mesaj göndərilir… - %1$s-nin dəvəti. Səbəb: %2$s %1$s dəvət olunmuş %2$s. Səbəb: %3$s %1$s sizi dəvət etdi. Səbəb: %2$s @@ -86,4 +68,5 @@ %1$s blokladı %2$s. Səbəb: %3$s %1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s %1$s %2$s dəvətini geri götürdü. Səbəb: %3$s - + Otağ yaratdınız + \ No newline at end of file From 48e9066df4c996092a61e02629547bf95210aebf Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Mon, 17 Oct 2022 18:52:54 +0000 Subject: [PATCH 0206/1068] Translated using Weblate (Catalan) Currently translated at 97.6% (2443 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- library/ui-strings/src/main/res/values-ca/strings.xml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index eddae8297e..fa363cee8c 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -1200,7 +1200,7 @@ Url: session_name: push_key: - app_id: + ID d\'aplicació: Revisa la configuració per activar les notificacions Estàs veient la notificació! Clica\'m! Vetat per %1$s @@ -2742,4 +2742,7 @@ Sol·licita que no es desi cap dada personalitzada del teclat en funció del que escrius a les converses (per exemple l\'historial d\'escriptura o el diccionari). Tingues en compte que alguns teclats poden no respectar aquesta configuració. Teclat incògnit 🔒 Has activat el xifrat a només en sessions verificades a totes les sales, a Configuració > Seguretat. - + Estat de verificació desconegut + Escaneja codi QR + ID de sessió: + \ No newline at end of file From 67344ff40e3cdf8aa7ec5deab3a07d9662fe7b50 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 18 Oct 2022 09:21:45 +0000 Subject: [PATCH 0207/1068] Translated using Weblate (Czech) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- .../src/main/res/values-cs/strings.xml | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index bc88530c3d..3410858988 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -1305,7 +1305,7 @@ \nKlíče nejsou důvěryhodné Křížové podpisování není zapnuto Aktivní relace - Ukázat všechny relace + Zobrazit všechny relace Správa relací Odhlásit se z této relace Žádná kryptografická informace není k dispozici @@ -2826,4 +2826,37 @@ \nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech. Vyzkoušejte rozšířený textový editor (textový režim již brzy) Povolit rozšířený textový editor + Ujistěte se, že znáte původ tohoto kódu. Propojením zařízení poskytnete někomu plný přístup ke svému účtu. + Potvrdit + Zkuste to znovu + Neshoduje se\? + Probíhá přihlašování + Připojování k zařízení + Naskenujte QR kód + Přihlašování na mobilním zařízením\? + Zobrazit QR kód na tomto zařízení + Vyberte možnost \"Naskenovat QR kód\" + Začněte na přihlašovací obrazovce + Vyberte možnost \"Přihlásit se pomocí QR kódu\" + Začněte na přihlašovací obrazovce + Vyberte možnost \"Zobrazit QR kód na tomto zařízení\" + Přejděte do Nastavení -> Zabezpečení a soukromí -> Zobrazit všechny relace + Otevřete ${app_name} na vašem druhém zařízení + Žádost byla na druhém zařízení zamítnuta. + Propojení nebylo dokončeno v požadovaném čase. + Propojení s tímto zařízením není podporováno. + Neúspěšné připojení + Zkontrolujte vaše přihlášené zařízení, měl by se zobrazit níže uvedený kód. Zkontrolujte, zda níže uvedený kód odpovídá danému zařízení: + Zabezpečené připojení navázáno + Pomocí odhlášeného zařízení naskenujte níže uvedený QR kód. + Pomocí přihlášeného zařízení naskenujte níže uvedený QR kód: + Přihlásit se pomocí QR kódu + Pomocí fotoaparátu na tomto zařízení naskenujte QR kód zobrazený na druhém zařízení: + Naskenovat QR kód + 3 + 2 + 1 + Pomocí tohoto zařízení se můžete přihlásit do mobilního nebo webového zařízení pomocí QR kódu. Můžete to provést dvěma způsoby: + Přihlásit se pomocí QR kódu + Naskenovat QR kód \ No newline at end of file From c1470618da23eae6be0ad3a3d1b420d3e9d81b43 Mon Sep 17 00:00:00 2001 From: Vri Date: Tue, 18 Oct 2022 04:05:52 +0000 Subject: [PATCH 0208/1068] Translated using Weblate (German) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 837a00e30b..fe849a1ddf 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2771,18 +2771,18 @@ ${app_name} braucht die Berechtigung, um Benachrichtigungen anzuzeigen. Benachrichtigungen können deine Nachrichten, Einladungen etc. anzeigen. \n \nBitte erlaube den Zugriff im nächsten Dialog, damit Benachrichtigungen angezeigt werden können. - Bitte vergewissere dich, dass du den Ursprung dieses Codes kennst. Durch die Verbindung beider Geräte, gewährst du Zugriff auf deinen gesamten Account. + Bitte vergewissere dich, dass du den Ursprung dieses Codes kennst. Durch Verbindung neuer Geräte gewährst du vollen Zugriff auf dein Konto. Bestätigen - Nochmal versuchen + Erneut versuchen Keine Übereinstimmung\? Du wirst angemeldet - Stelle Verbindung zum Gerät her - QR-Code scannen + Verbinde mit Gerät + QR-Code einlesen Mobiles Gerät anmelden\? QR-Code auf diesem Gerät anzeigen - Wähle \'QR-Code scannen\' + Wähle „QR-Code einlesen“ Beginne auf dem Anmeldebildschirm - Wähle \'Mit QR-Code anmelden\' + Wähle „Mit QR-Code anmelden“ Beginne auf dem Anmeldebildschirm Wähle \'QR-Code auf diesem Gerät anzeigen\' Gehe zu Einstellungen -> Sicherheit und Privatsphäre -> Alle Sitzungen anzeigen @@ -2793,8 +2793,8 @@ Verbindung fehlgeschlagen Überprüfe dein angemeldetes Gerät. Der unten gezeigte Code sollte angezeigt werden. Bestätige, dass beide Codes übereinstimmen: Sichere Verbindung hergestellt - Scanne den unten angezeigten QR-Code mit deinem nicht angemeldeten Gerät. - Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code zu scannen: + Lese den unten angezeigten QR-Code mit deinem nicht angemeldeten Gerät ein. + Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code einzulesen: Mit QR-Code anmelden Benutze die Kamera auf diesem Gerät um den vom anderen Gerät angezeigten QR-Code zu scannen: QR-Code scannen From ef0e2ccaca2161516ec07fceb2f3d2ecb36507b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 18 Oct 2022 20:57:46 +0000 Subject: [PATCH 0209/1068] Translated using Weblate (Estonian) Currently translated at 99.6% (2495 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- .../src/main/res/values-et/strings.xml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index ecb7b9757c..7ead21394c 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2735,4 +2735,65 @@ Selge lugu Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim) Võta kasutusele vormindatud teksti pruukiv tekstitoimeti + Vaata seadet, kus sa oled Matrix\'i võtku loginud - seal peaks nüüd kuvatama QR-koodi. Kinnita, et allpool toodud QR-kood on sama kui tolles seadmes kuvatav kood: + Sa võid seda seadet kasutada nutiseadme või veebirakenduse sisselogimiseks QR-koodi alusel. Sa saad seda teha kahel moel: + Kasuta allajoonitud kirja + Kasuta läbijoonitud kirja + Kasuta kaldkirja + Kasuta paksu kirja + Palun vaata, et sa kindlasti tead, kust see QR-kood kuvatakse. Sellisel viisil seadmete sidumisel sa annad oma kasutajakontole täiemahulise ligipääsu. + Kinnita + Proovi uuesti + Ei klapi\? + Logime sind võrku + Loon ühendust seadmega + Loe QR-koodi + Kas logid sisse nutiseadmest\? + Näita selles seadmes QR-koodi + Vali „Loe QR-koodi“ + Alusta sisselogimisvaatest + Vali „Logi võrku QR-koodi abil“ + Alusta sisselogimisvaatest + Vali „Näita selles seadmes QR-koodi“ + Ava Seadistused -> Turvalisus ja privaatsus -> Näita kõiki sessioone + Ava ${app_name} oma teises seades + Teine seade lükkas päringu tagasi. + Sidumine ei lõppenud etteantud aja jooksul. + Sidumine selle seadmega ei ole toetatud. + Seoste loomine ei õnnestunud + Turvaline ühendus on olemas + Loe QR-koodi seadmega, kus sa oled Matrix\'i võrgust välja loginud. + Järgneva QR-koodi skaneerimiseks kasuta seadet, kus sa oled Matrix\'i võrku loginud: + Logi sisse QR-koodi abil + Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel: + Loe QR-koodi + 3 + 2 + 1 + Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi. + Luba klientrakenduse teabe salvestamine + Sellega saad parema ülevaate oma sessioonidest ja võimaluse neid mugavasti hallata. + Kasuta uut sessioonihaldurit + Logi sisse QR-koodi abil + Operatsioonisüsteem + Mudel + Brauser + URL + Versioon + Nimi + Rakendus + Luba selles sessioonis tõuketeavitused. + Tõuketeavitused + Selle sessiooni olekut ei saa tuvastada enne kui oled ta verifitseerinud. + Verifitseerimise olek on määratlemata + Loe QR-koodi + Kasutusel: + Sessiooni tunnus: + Midagi läks nüüd sassi. Palun kontrolli oma seadme võrguühendust ja proovi uuesti. + Anna õigused + ${app_name} vajab teavituste näitamiseks õigusi. +\nPalun luba vastavad õigused. + ${app_name} vajab teavituste näitamiseks õigusi. Teavituste sisuks võivad olla sulle saadetud sõnumid, kutsed ja muud olulist. +\n +\nJärgmistes vaadetes palun anna sellele rakendusele teavituste kuvamiseks vajalikud õigused. \ No newline at end of file From ae2793e0c84b016b3d2811231ee8f9e940f2faa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Tue, 18 Oct 2022 18:04:45 +0000 Subject: [PATCH 0210/1068] Translated using Weblate (Hungarian) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 57510e55a9..93015388f4 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -569,7 +569,7 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés Alapszintű diagnosztika nem talált hibát. Ha még mindig nem kapsz értesítéseket, kérlek küldj egy hiba jegyet amivel segítheted a hibakeresésünket. Egy vagy több teszt is sikertelen volt, próbáld ki a javasolt javítást, javításokat. Egy vagy több teszt sikertelenül végződött, kérlek küldj egy hibabejelentést ami segít nekünk a problémát kivizsgálni. - Rendszer beállítások. + Rendszerbeállítások. Az értesítések engedélyezve vannak a rendszerbeállításokban. Az értesítések tiltva vannak a rendszerbeállításokban. Kérlek ellenőrizd a rendszerbeállításokat. @@ -582,7 +582,7 @@ Kérlek ellenőrizd a fiókbeállításokat.
Munkamenet beállítások. Az értesítések engedélyezve vannak ezen az munkameneten. Az értesítések tiltva vannak ezen a munkameneten. Kérlek ellenőrizd a ${app_name} beállításokat. - Engedélyez + Engedélyezés Play Szolgáltatások ellenőrzése Google Play Services APK elérhető és a legújabb verziójú. "${app_name} a Google Play Services-t használja a „push” értesítések fogadásához, de úgy tűnik az nincs megfelelően beállítva: @@ -824,7 +824,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Már nézed ezt a szobát! Általános Beállítások - Biztonság & Adatvédelem + Biztonság és adatvédelem „Push” szabályok „Push” szabályok nincsenek „Push” átjárók nincsenek regisztrálva @@ -834,8 +834,8 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Munkamenet képernyő név: Url: Formátum: - Hang & Videó - Segítség & Névjegy + Hang és videó + Súgó és névjegy Token regisztrálása Javaslat tétel A javaslatodat kérlek ír le alulra. From ee655a38402f0b796c1e5e86b841f1d7f061766b Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 17 Oct 2022 18:43:51 +0000 Subject: [PATCH 0211/1068] Translated using Weblate (Hungarian) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- .../src/main/res/values-hu/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 93015388f4..59792a9218 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2772,4 +2772,37 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód) Vizuális szerkesztő engedélyezése Értem + Nem egyezik\? + Bejelentkeztetés + Mobil eszközzel jelentkezel be\? + Kezd a bejelentkező képernyőn + Kezd a bejelentkező képernyőn + Nézd meg a már bejelentkezett eszközödet, az alábbi kódot kell megjelenítenie. Erősítsd meg, hogy az alábbi kód megegyezik a másik eszközön láthatóval: + Használd a már belépett eszközt az alábbi QR kód beolvasásához: + Ezzel az eszközzel, QR kód segítségével, bejelentkezhetsz mobil és webes munkamenetbe. Két lehetőséged is van: + Győződj meg a kód eredetéről. Az eszközök összekötésével esetleg valakinek teljes hozzáférést adhatsz a fiókodhoz. + Megerősítés + Próbáld újra + Csatlakozás az eszközhöz + QR kód beolvasása + QR kód megjelenítése ezen az eszközön + Válaszd ezt: „QR kód beolvasása” + Válaszd ezt: „Belépés QR kóddal” + Válaszd ezt: „QR kód megjelenítése ezen az eszközön” + Menj a Beállítások -> Biztonság és Adatvédelem -> Minden munkamenet megjelenítése menübe + Nyisd meg a(z) ${app_name} alkalmazást a másik eszközön + A kérést elutasították a másik eszközön. + Az összekötés az elvárt időn belül nem fejeződött be. + Összekötés ezzel az eszközzel nem támogatott. + Kapcsolat sikertelen + Biztonságos kapcsolat beállítva + A kijelentkezett eszközzel olvasd be a QR kódot alább. + Belépés QR kóddal + Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására: + QR kód beolvasása + 3 + 2 + 1 + Belépés QR kóddal + QR kód beolvasása \ No newline at end of file From f1f70c939f1511b44ef610c824a1760ae1fe10d8 Mon Sep 17 00:00:00 2001 From: Linerly Date: Tue, 18 Oct 2022 07:01:50 +0000 Subject: [PATCH 0212/1068] Translated using Weblate (Indonesian) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- .../src/main/res/values-in/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 137805f8b0..6d20563d55 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2720,4 +2720,37 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \nMohon perbolehkan akses di munculan berikutnya untuk dapat melihat notifikasi.
Coba editor teks kaya (mode teks biasa akan datang) Aktifkan editor teks kaya + Pastikan Anda tahu asal kode ini. Dengan menautkan perangkat, Anda akan memberikan seseorang akses penuh ke akun Anda. + Konfirmasi + Coba lagi + Tidak cocok\? + Memasukkan Anda + Menghubungkan ke perangkat + Pindai kode QR + Ingin masuk di perangkat ponsel\? + Tampilkan kode QR di perangkat ini + Pilih \'Pindai dengan kode QR\' + Mulai dari layar masuk + Pilih \'Masuk dengan kode QR\' + Mulai dari layar masuk + Pilih \'Tampilkan kode QR di perangkat ini\' + Pergi ke Pengaturan → Keamanan & Privasi → Tampilkan Semua Sesi + Buka ${app_name} di perangkat Anda yang lain + Permintaan ditolak di perangkat lain. + Penautan tidak selesai dalam waktu yang dibutuhkan. + Penautan dengan perangkat ini tidak didukung. + Koneksi tidak berhasil + Periksa perangkat yang masuk, kode di bawah seharusnya ditampilkan. Konfirmasi bahwa kode di bawah cocok dengan perangkat itu: + Koneksi aman dibuat + Pindai kode QR di bawah dengan perangkat Anda yang telah keluar dari akun. + Gunakan perangkat yang sudah masuk untuk memindai kode QR di bawah: + Masuk dengan kode QR + Gunakan kamera pada perangkat ini untuk memindai kode QR yang ditampilkan pada perangkat Anda yang lain: + Pindai kode QR + 3 + 2 + 1 + Anda dapat menggunakan perangkat ini untuk masuk ke perangkat ponsel atau web dengan sebuah kode QR. Ada dua cara untuk melalukan ini: + Masuk dengan Kode QR + Pindai kode QR \ No newline at end of file From c12b21fb8c91ac6acf42842aafffa32893cabdc8 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 18 Oct 2022 10:07:30 +0000 Subject: [PATCH 0213/1068] Translated using Weblate (Italian) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- .../src/main/res/values-it/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index 4bd4a5401c..cea69030bc 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2763,4 +2763,37 @@ \nConsenti l\'accesso nelle prossime schermate per potere vedere la notifica.
Prova l\'editor in rich text (il testo semplice è in arrivo) Attiva editor in rich text + Assicurati di conoscere l\'origine di questo codice. Collegando i dispositivi, fornirai a qualcuno l\'accesso totale al tuo account. + Conferma + Riprova + Non corrisponde\? + Accesso in corso + Connessione al dispositivo + Scansiona codice QR + Effettuare l\'accesso in un dispositivo mobile\? + Mostra codice QR in questo dispositivo + Seleziona \'Scansiona codice QR\' + Inizia nella schermata di accesso + Seleziona ‘Accedi con codice QR’ + Inizia nella schermata di accesso + Seleziona ‘Mostra codice QR in questo dispositivo’ + Vai in Impostazioni -> Sicurezza e privacy -> Mostra tutte le sessioni + Apri ${app_name} sull\'altro dispositivo + La richiesta è stata negata sull\'altro dispositivo. + Il collegamento non è stato completato nel tempo previsto. + Il collegamento con questo dispositivo non è supportato. + Connessione non riuscita + Controlla il dispositivo che ha l\'accesso, dovresti vedere il codice sotto. Conferma che il codice corrisponda con quel dispositivo: + Connessione sicura stabilita + Scansiona il codice QR sottostante con il dispositivo che è disconnesso. + Usa il dispositivo che ha l\'accesso per scansionare il codice QR sotto: + Accedi con codice QR + Usa la fotocamera di questo dispositivo per scansionare il codice QR mostrato nell\'altro dispositivo: + Scansiona codice QR + 3 + 2 + 1 + Puoi usare questo dispositivo per accedere in un dispositivo mobile o web con un codice QR. Ci sono due modi: + Accedi con codice QR + Scansiona codice QR \ No newline at end of file From 19164c235289e5de47d36d8c4a03cc4968bfc727 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 19 Oct 2022 09:30:27 +0000 Subject: [PATCH 0214/1068] Translated using Weblate (Japanese) Currently translated at 89.2% (2233 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 120 ++++++++++++++++-- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 3e817e398c..37c0bca52f 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -987,7 +987,7 @@ プッシュ通知のテスト FCMトークンのホームサーバーへの登録に失敗しました: \n%1$s - FCMトークンのホームサーバーへの登録が成功しました。 + FCMトークンがホームサーバーに登録されました。 トークンの登録 アカウントを追加 [%1$s] @@ -1233,7 +1233,7 @@ 続行するには利用規約を承認してください ホームサーバーの利用規約を承認したら、再試行してください。 次に - 次に + 次へ 次に 次に 次に @@ -1282,9 +1282,9 @@ 提案の送信に失敗しました(%s) ありがとうございます、提案は正常に送信されました トークンの登録 - app_display_name: - app_id: - push_key: + アプリケーションの表示名: + App ID: + Push Key: 登録されたプッシュゲートウェイはありません プッシュ通知に関するルールが定義されていません プッシュ通知に関するルール @@ -1383,8 +1383,8 @@ 同意を撤回 あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。 メールと電話番号を送信 - %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください - %sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください + %sにメールを送りました。メールを確認してリンクをクリックしてください + %sにメールを送りました。メールの確認リンクをクリックしてください 発見可能な電話番号 IDサーバーとの接続を解除すると、他のユーザーによって発見されなくなり、また、メールアドレスや電話で他のユーザーを招待することができなくなります。 電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。 @@ -1412,7 +1412,7 @@ 提案する フォーマット: URL: - セッション名: + セッションの表示名: 以下のうちいずれかが流出、あるいはハッキングされた恐れがあります。 \n \n- あなたのパスワード @@ -1696,7 +1696,7 @@ メッセージを送る… このファイルは大きすぎてアップロードできません。 この情報の送信に同意しますか? - 連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 + 連絡先を発見するには、連絡先のデータ(メールアドレスと電話番号)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 メールアドレスと電話番号を%sに送信 このIDサーバーは運営方針を提供していません IDサーバーの運営方針を隠す @@ -2359,4 +2359,104 @@ ベータ版 ベータ版 試す - + オフラインモード + 新着はありません。 + - ユーザーの無視が解除されました + 試してみる + 右上をタップするとフィードバックを送信するオプションが表示されます。 + フィードバックを送信 + 右下からスペースにより早く簡単にアクセスできます。 + スペースにアクセス + ${app_name}をシンプルにするために、タブはオプションになりました。右上のメニューから管理できます。 + 新しいレイアウトにようこそ! + アニメーション画像を自動再生 + エンドポイントのホームサーバーへの登録に失敗しました: +\n%1$s + エンドポイントがホームサーバーに登録されました。 + エンドポイントの登録 + 権限を与える + ${app_name}は通知の表示に権限が必要です。 +\n権限を与えてください。 + + %1$sと他%2$d名 + + %1$sと%2$s + ホームサーバーがサポートしていないため、スレッド機能は不安定かもしれません。スレッドのメッセージは安定して表示されないおそれがあります。%sスレッド機能を有効にしてよろしいですか? + スレッド(ベータ版) + スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。%sスレッドを有効にするとアプリケーションが再起動します。再起動には時間がかかる可能性があります。 + スレッド(ベータ版) + ${app_name}は通知を表示するために許可を必要としています。通知にはメッセージや招待などが表示されます。 +\n +\n通知を表示するには、次のポップアップでアクセスを許可してください。 + メールアドレスが認証されていません。メールボックスを確認してください + 画面共有を停止 + 画面を共有 + 招待 + プッシュ通知 + セッション名 + セッションを改名 + IPアドレス + オペレーティングシステム + 形式 + ブラウザー + URL + バージョン + 名称 + アプリケーション + このステップをスキップ + 問題ありません! + 進みましょう + ユーザー名 / メールアドレス / 電話番号 + あなたは人間ですか? + %sに送信された手順に従ってください + パスワードを再設定 + パスワードを忘れた場合 + 電子メールを再送信 + 電子メールが届いていませんか? + %sに送信された手順に従ってください + メールアドレスを認証 + コードを再送信 + コードが%sに送信されました + 電話番号を確認してください + 全ての端末からサインアウト + パスワードを再設定 + パスワードは8文字以上に設定してください。 + パスワードを選択 + 新しいパスワード + 電子メールを確認してください。 + %sは認証リンクを送信します + 確認コード + 電話番号 + %sはアカウントの認証が必要です + 電話番号を入力してください + メールアドレス + %sはアカウントの認証が必要です + リッチテキストエディターを有効にする + 最初のメッセージを送信する際にダイレクトメッセージを作成 + 遅延DMを有効にする + スペースがありません。 + 新しいレイアウトを有効にする + アクティビティー順 + アルファベット順 + 並び替え + フィルターを表示 + レイアウトの設定 + 了解 + 次へ + 詳しく知る + + + + ${app_name}は以下の理由で、キャッシュを消去して最新の状態にする必要があります。 +\n%s +\n +\nアプリケーションが再起動します。再起動には時間がかかる可能性があります。 + 初期同期のリクエスト + %sの子スペースを折りたたむ + %sの子スペースを展開 + ルームを探索 + スペースを変更 + ルームを作成 + チャットを開始 + 全ての会話 + \ No newline at end of file From 225627fbfc3dcdfc419f50377279c5e39d24cd74 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Tue, 18 Oct 2022 23:42:10 +0000 Subject: [PATCH 0215/1068] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- .../src/main/res/values-pt-rBR/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 7a709757c9..97141a9765 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2772,4 +2772,37 @@ \nPor favor permita acesso nos próximos pop-ups para ser capaz de visualizar notificação. Experimente o editor de texto rico (modo de texto puro vindo em breve) Habilitar editor de texto rico + Por favor assegure que você sabe a origem deste código. Ao linkar dispositivos, você vai prover alguém com acesso completo a sua conta. + Confirmar + Tentar de novo + Nenhuma correspondência\? + Fazendo-lhe signin + Conectando a dispositivo + Scannar QR code + Fazendo signin com um dispositivo móvel\? + Mostrar QR code neste dispositivo + Selecione \'Scannar QR code\' + Comece na tela de signin + Selecione \'Fazer signin com QR code\' + Comece na tela de signin + Selecione \'Mostrar QR code neste dispositivo\' + Vá para Configurações -> Segurança & Privacidade -> Mostrar Todas as Sessões + Obra ${app_name} em seu outro dispositivo + A requisição foi negada no outro dispositivo. + A linkagem não foi completada no tempo requerido. + Linkagem com este dispositivo não é suportado. + Conexão malsucedida + Cheque seu dispositivo feito signin, o código abaixo deveria ser exibido. Confirme que o código abaixo corresponde com esse dispositivo: + Conexão segura estabelecida + Scanne o QR code abaixo com seu dispositivo que está feito signout. + Use seu dispositivo feito signin para scannar o QR code abaixo: + Fazer signin com QR code + Use a câmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo: + Scannar QR code + 3 + 2 + 1 + Você pode usar este dispositivo para fazer signin com um dispositivo móvel ou web com um QR code. Existem duas maneiras de fazer isto: + Fazer signin com QR Code + Scannar QR code \ No newline at end of file From 27b07e8b5ce2a1dc224e146f4fb69666fb37bbdb Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Wed, 19 Oct 2022 08:55:14 +0000 Subject: [PATCH 0216/1068] Translated using Weblate (Russian) Currently translated at 95.7% (2396 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- .../src/main/res/values-ru/strings.xml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 1953e46698..0349acedd1 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -1701,7 +1701,7 @@ Этот номер телефона уже используется. В ваш аккаунт не добавлен номер телефона Адрес электронной почты - В ваш аккаунт не добавлен адрес электронной почты + В вашу учётную запись не добавлен адрес электронной почты Телефонные номера Удалить %s\? Убедитесь, что вы перешли по ссылке в электронном письме, которое мы вам отправили. @@ -2777,4 +2777,19 @@ Модель Операционная система Новый менеджер сессий + + Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете. + + Результаты будут видны после завершения опроса + Доступ к пространствам (внизу справа) быстрее и проще, чем когда-либо прежде. + Доступ к пространствам + + Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете. + Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете. + \ No newline at end of file From 5869c273bc550c814ea7f2bb5cb0bf6b92bfb99a Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Mon, 17 Oct 2022 21:16:47 +0000 Subject: [PATCH 0217/1068] Translated using Weblate (Slovak) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- .../src/main/res/values-sk/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index ac2c4bea4a..43a8301d58 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2826,4 +2826,37 @@ \nPovoľte prístup na ďalších vyskakovacích oknách, aby ste mohli zobrazovať oznámenia. Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro) Povoliť rozšírený textový editor + Uistite sa prosím, že poznáte pôvod tohto kódu. Prepojením zariadení poskytnete niekomu plný prístup k svojmu účtu. + Potvrdiť + Skúste to znova + Nezhoduje sa\? + Prebieha prihlasovanie + Pripájanie k zariadeniu + Skenovať QR kód + Prihlasovanie do mobilného zariadenia\? + Zobraziť QR kód na tomto zariadení + Vyberte možnosť \"Skenovať QR kód\" + Začnite na prihlasovacej obrazovke + Vyberte možnosť \"Prihlásiť sa pomocou QR kódu\" + Začnite na prihlasovacej obrazovke + Vyberte možnosť \"Zobraziť QR kód na tomto zariadení\" + Prejdite do Nastavenia -> Zabezpečenie a súkromie -> Zobraziť všetky relácie + Otvorte ${app_name} na vašom druhom zariadení + Žiadosť bola na druhom zariadení zamietnutá. + Prepojenie nebolo dokončené v požadovanom čase. + Prepojenie s týmto zariadením nie je podporované. + Neúspešné pripojenie + Skontrolujte svoje prihlásené zariadenie, mal by sa zobraziť nasledujúci kód. Skontrolujte, či sa nižšie uvedený kód zhoduje s daným zariadením: + Zabezpečené pripojenie bolo vytvorené + Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené. + Pomocou prihláseného zariadenia naskenujte nižšie uvedený QR kód: + Prihlásiť sa pomocou QR kódu + Pomocou fotoaparátu na tomto zariadení naskenujte QR kód zobrazený na vašom druhom zariadení: + Skenovať QR kód + 3 + 2 + 1 + Pomocou tohto zariadenia sa môžete prihlásiť do mobilného alebo webového zariadenia pomocou QR kódu. Môžete to urobiť dvoma spôsobmi: + Prihlásiť sa pomocou QR kódu + Skenovať QR kód \ No newline at end of file From c2600dc5356fc767bc81ba2c6d3d42f45c854c6e Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Mon, 17 Oct 2022 19:21:42 +0000 Subject: [PATCH 0218/1068] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- .../src/main/res/values-uk/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 20b7348bdf..2a04e58f41 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2880,4 +2880,37 @@ \nДозвольте доступ до наступних спливних вікон, щоб мати змогу переглядати сповіщення.
Спробуйте розширений текстовий редактор (незабаром з\'явиться режим звичайного тексту) Увімкнути розширений текстовий редактор + Переконайтеся, що ви знаєте походження цього коду. Пов\'язавши пристрої, ви надасте будь-кому повний доступ до свого облікового запису. + Підтвердити + Повторити спробу + Не збігається\? + Вхід + Під\'єднання до пристрою + Входите на мобільному пристрої\? + Показати QR-код на цьому пристрої + Виберіть «Сканувати QR-код» + Виберіть «Увійти за допомогою QR-коду» + Почніть з екрана входу + Почніть з екрана входу + Виберіть «Показати QR-код на цьому пристрої» + Перейдіть до Налаштування -> Безпека й приватність -> Показати всі сеанси + Відкрийте ${app_name} на іншому своєму пристрої + Запит на іншому пристрої було відхилено. + Пов\'язування не було завершено у встановлені терміни. + Пов\'язування з цим пристроєм не підтримується. + Невдале з\'єднання + Перевірте свій пристрій, на якому ви ввійшли. На екрані повинен з\'явитися код, наведений нижче. Переконайтеся, що наведений код збігається з кодом на вашому пристрої: + Безпечне з\'єднання встановлено + Зіскануйте QR-код нижче своїм пристроєм, з якого ви вийшли. + Скануйте QR-код нижче за допомогою свого пристрою для входу: + Увійти за допомогою QR-коду + Використовуйте камеру цього пристрою, щоб зісканувати QR-код, показаний на іншому пристрої: + 3 + 2 + 1 + За допомогою цього пристрою ви можете ввійти на мобільному або вебпристрої за допомогою QR-коду. Зробити це можна двома способами: + Увійти за допомогою QR-коду + Сканувати QR-код + Сканувати QR-код + Сканувати QR-код \ No newline at end of file From 098aaf9d5c1e995839137603de142d9b796c4c80 Mon Sep 17 00:00:00 2001 From: phardyle Date: Tue, 18 Oct 2022 01:51:18 +0000 Subject: [PATCH 0219/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.7% (2371 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 39992ff418..ae29132c91 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -2623,4 +2623,6 @@ 仅在首条消息创建私聊消息 启用延迟的私聊消息 简化的Element,带有可选的标签 - + 无痕键盘 + 要求键盘不要基于你在对话中的输入更新任何个性化数据,如输入历史和字典。请注意,某些键盘可能不会遵守此设置。 + \ No newline at end of file From b6a33d1b7a1ed0c734607b9a6c17a7a2e9db92cd Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 18 Oct 2022 03:25:50 +0000 Subject: [PATCH 0220/1068] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2503 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- .../src/main/res/values-zh-rTW/strings.xml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index b72f9bc5c6..c9057cd289 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2718,4 +2718,37 @@ \n請在下一個彈出式視窗允許存取以檢視通知。
試用格式化文字編輯器(純文字模式即將推出) 啟用格式化文字編輯器 + 請確保您知道此驗證碼的來源。透過連結裝置,您將為某人提供對您帳號的完整存取權限。 + 確認 + 再試一次 + 不相符? + 登入 + 連線至裝置 + 掃描 QR code + 正在使用行動裝置登入? + 在此裝置顯示 QR code + 選取「掃描 QR code」 + 從登入畫面開始 + 選取「使用 QR code 登入」 + 從登入畫面開始 + 選取「在此裝置上顯示 QR code」 + 到「設定」→「安全與隱私」→「顯示所有工作階段」 + 在您的其他裝置上開啟 ${app_name} + 請求在另一台裝置上被拒絕。 + 連結未在規定時間內完成。 + 不支援與其裝置連結。 + 連線不成功 + 請檢查您已登入的裝置,應該會顯示以下驗證碼。請確認以下驗證碼與該裝置相符: + 已建立安全連線 + 使用您已登出的裝置掃描以下 QR code。 + 使用您已登入的裝置來掃描下方的 QR code: + 使用 QR code 登入 + 使用此裝置的相機掃描您其他裝置上顯示的 QR code: + 掃描 QR code + 3 + 2 + 1 + 您可以使用此裝置透過 QR code 登入移動裝置或網路裝置。有兩種方法可以作到: + 使用 QR code 登入 + 掃描 QR code \ No newline at end of file From 4b63f1db82768deba93456af9a47d6852e959518 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 18 Oct 2022 18:07:36 +0000 Subject: [PATCH 0221/1068] Translated using Weblate (Hebrew) Currently translated at 84.7% (2121 of 2503 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/he/ --- library/ui-strings/src/main/res/values-iw/strings.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml index ff19310c8e..b9f81ae446 100644 --- a/library/ui-strings/src/main/res/values-iw/strings.xml +++ b/library/ui-strings/src/main/res/values-iw/strings.xml @@ -861,9 +861,7 @@ בחר שרת בית מותאם אישית בחר שירותי מטריקס אלמנט בחר matrix.org - חשבונך טרם נוצר. -\n -\nלהפסיק את תהליך ההרשמה\? + חשבונך טרם נוצר. להפסיק את תהליך ההרשמה\? אזהרה שם המשתמש הזה תפוס הבא @@ -2304,7 +2302,7 @@ קהילות צוותים חברים ומשפחה - נעזור לך להתחבר. + נעזור לך להתחבר עם מי תדברו הכי הרבה\? מוצפן מקצה לקצה ואין צורך במספר טלפון. ללא פרסומות או עיבוד נתונים. בחר היכן השיחות שלך נשמרות, נותן לך שליטה ועצמאות. מחובר דרך Matrix. @@ -2508,4 +2506,4 @@ \nזה יהיה מעבר חד פעמי שכן שרשורים הם כעת חלק ממפרט Matrix.
שיתוף מסך של ${app_name} המסך משותף כרגע - + \ No newline at end of file From 21c30c488c245bfc7441c127159369a7c4116cc8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 17:07:11 +0200 Subject: [PATCH 0222/1068] Towncrier --- CHANGES.md | 61 ++++++++++++++++++++++++++++++++++++++++ changelog.d/5968.bugfix | 1 - changelog.d/7217.wip | 1 - changelog.d/7257.wip | 1 - changelog.d/7261.wip | 1 - changelog.d/7273.wip | 1 - changelog.d/7277.wip | 1 - changelog.d/7281.wip | 1 - changelog.d/7282.sdk | 1 - changelog.d/7283.wip | 1 - changelog.d/7285.misc | 1 - changelog.d/7288.feature | 1 - changelog.d/7288.sdk | 10 ------- changelog.d/7294.wip | 1 - changelog.d/7300.wip | 1 - changelog.d/7310.bugfix | 1 - changelog.d/7321.wip | 1 - changelog.d/7324.wip | 1 - changelog.d/7327.wip | 1 - changelog.d/7335.misc | 1 - changelog.d/7336.feature | 1 - changelog.d/7338.wip | 1 - changelog.d/7344.feature | 1 - changelog.d/7354.misc | 1 - changelog.d/7358.sdk | 1 - changelog.d/7359.bugfix | 1 - changelog.d/7359.sdk | 1 - changelog.d/7363.wip | 1 - changelog.d/7372.bugfix | 1 - changelog.d/7374.feature | 1 - changelog.d/7384.misc | 1 - changelog.d/7387.wip | 1 - changelog.d/7393.wip | 1 - changelog.d/7397.wip | 1 - 34 files changed, 61 insertions(+), 42 deletions(-) delete mode 100644 changelog.d/5968.bugfix delete mode 100644 changelog.d/7217.wip delete mode 100644 changelog.d/7257.wip delete mode 100644 changelog.d/7261.wip delete mode 100644 changelog.d/7273.wip delete mode 100644 changelog.d/7277.wip delete mode 100644 changelog.d/7281.wip delete mode 100644 changelog.d/7282.sdk delete mode 100644 changelog.d/7283.wip delete mode 100644 changelog.d/7285.misc delete mode 100644 changelog.d/7288.feature delete mode 100644 changelog.d/7288.sdk delete mode 100644 changelog.d/7294.wip delete mode 100644 changelog.d/7300.wip delete mode 100644 changelog.d/7310.bugfix delete mode 100644 changelog.d/7321.wip delete mode 100644 changelog.d/7324.wip delete mode 100644 changelog.d/7327.wip delete mode 100644 changelog.d/7335.misc delete mode 100644 changelog.d/7336.feature delete mode 100644 changelog.d/7338.wip delete mode 100644 changelog.d/7344.feature delete mode 100644 changelog.d/7354.misc delete mode 100644 changelog.d/7358.sdk delete mode 100644 changelog.d/7359.bugfix delete mode 100644 changelog.d/7359.sdk delete mode 100644 changelog.d/7363.wip delete mode 100644 changelog.d/7372.bugfix delete mode 100644 changelog.d/7374.feature delete mode 100644 changelog.d/7384.misc delete mode 100644 changelog.d/7387.wip delete mode 100644 changelog.d/7393.wip delete mode 100644 changelog.d/7397.wip diff --git a/CHANGES.md b/CHANGES.md index d1e4834988..21d58026e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,64 @@ +Changes in Element v1.5.4 (2022-10-19) +====================================== + +Features ✨ +---------- + - Add WYSIWYG editor. ([#7288](https://github.com/vector-im/element-android/issues/7288)) + - [Device management] Add lab flag for the feature ([#7336](https://github.com/vector-im/element-android/issues/7336)) + - [Device management] Add lab flag for matrix client info account data event ([#7344](https://github.com/vector-im/element-android/issues/7344)) + - [Device Management] Redirect to the new screen everywhere when lab flag is on ([#7374](https://github.com/vector-im/element-android/issues/7374)) + +Bugfixes 🐛 +---------- + - Fix wrong mic button direction to cancel on RTL languages ([#5968](https://github.com/vector-im/element-android/issues/5968)) + - [Device Management] Long session names not handled well ([#7310](https://github.com/vector-im/element-android/issues/7310)) + - Fix editing formatted messages with plain text editor ([#7359](https://github.com/vector-im/element-android/issues/7359)) + - Handle properly when getUser returns null - prefer using getUserOrDefault ([#7372](https://github.com/vector-im/element-android/issues/7372)) + +In development 🚧 +---------------- + - Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API ([#7217](https://github.com/vector-im/element-android/issues/7217)) + - [Device Management] Save "matrix_client_information" events on login/registration ([#7257](https://github.com/vector-im/element-android/issues/7257)) + - Adds pusher toggle setting to device manager v2 ([#7261](https://github.com/vector-im/element-android/issues/7261)) + - [Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget ([#7273](https://github.com/vector-im/element-android/issues/7273)) + - [Device Management] Show correct device type icons ([#7277](https://github.com/vector-im/element-android/issues/7277)) + - Links "Enable Notifications for this session" setting to enabled value in pusher ([#7281](https://github.com/vector-im/element-android/issues/7281)) + - [Voice Broadcast] Aggregate state events in the timeline ([#7283](https://github.com/vector-im/element-android/issues/7283)) + - [Device Management] Render extended device info ([#7294](https://github.com/vector-im/element-android/issues/7294)) + - Implements client-side of local notification settings event ([#7300](https://github.com/vector-im/element-android/issues/7300)) + - [Device management] Improve the parsing for OS of Desktop/Web sessions ([#7321](https://github.com/vector-im/element-android/issues/7321)) + - [Device management] Hide the IP address and last activity date on current session ([#7324](https://github.com/vector-im/element-android/issues/7324)) + - [Device management] Update the unknown verification status icon ([#7327](https://github.com/vector-im/element-android/issues/7327)) + - Implement QR Code Login UI ([#7338](https://github.com/vector-im/element-android/issues/7338)) + - [Voice Broadcast] Record and send non aggregated voice messages to the room ([#7363](https://github.com/vector-im/element-android/issues/7363)) + - [Voice Broadcast] Start listening to a voice broadcast ([#7387](https://github.com/vector-im/element-android/issues/7387)) + - [Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) ([#7393](https://github.com/vector-im/element-android/issues/7393)) + - [Voice Broadcast] Add additional data in events ([#7397](https://github.com/vector-im/element-android/issues/7397)) + +SDK API changes ⚠️ +------------------ + - Stop using `original_event` field from `/relations` endpoint ([#7282](https://github.com/vector-im/element-android/issues/7282)) + - Add `formattedText` or similar optional parameters in several methods: + + * RelationService: + * editTextMessage + * editReply + * replyToMessage + * SendService: + * sendQuotedTextMessage + + This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. ([#7288](https://github.com/vector-im/element-android/issues/7288)) + - Add support for `m.login.token` auth during QR code based sign in ([#7358](https://github.com/vector-im/element-android/issues/7358)) + - Allow getting the formatted or plain text body of a message for the fun `TimelineEvent.getTextEditableContent()`. ([#7359](https://github.com/vector-im/element-android/issues/7359)) + +Other changes +------------- + - Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. ([#7285](https://github.com/vector-im/element-android/issues/7285)) + - Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead. ([#7335](https://github.com/vector-im/element-android/issues/7335)) + - Update WYSIWYG editor designs. ([#7354](https://github.com/vector-im/element-android/issues/7354)) + - Update WYSIWYG library to v0.2.1. ([#7384](https://github.com/vector-im/element-android/issues/7384)) + + Changes in Element v1.5.2 (2022-10-05) ====================================== diff --git a/changelog.d/5968.bugfix b/changelog.d/5968.bugfix deleted file mode 100644 index 05cf5cea60..0000000000 --- a/changelog.d/5968.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix wrong mic button direction to cancel on RTL languages diff --git a/changelog.d/7217.wip b/changelog.d/7217.wip deleted file mode 100644 index a8cc2a3ef3..0000000000 --- a/changelog.d/7217.wip +++ /dev/null @@ -1 +0,0 @@ -Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API diff --git a/changelog.d/7257.wip b/changelog.d/7257.wip deleted file mode 100644 index c6f9aefbd8..0000000000 --- a/changelog.d/7257.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Save "matrix_client_information" events on login/registration diff --git a/changelog.d/7261.wip b/changelog.d/7261.wip deleted file mode 100644 index f7063fcc1b..0000000000 --- a/changelog.d/7261.wip +++ /dev/null @@ -1 +0,0 @@ -Adds pusher toggle setting to device manager v2 diff --git a/changelog.d/7273.wip b/changelog.d/7273.wip deleted file mode 100644 index c480a79a43..0000000000 --- a/changelog.d/7273.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget diff --git a/changelog.d/7277.wip b/changelog.d/7277.wip deleted file mode 100644 index 168d10b809..0000000000 --- a/changelog.d/7277.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Show correct device type icons diff --git a/changelog.d/7281.wip b/changelog.d/7281.wip deleted file mode 100644 index c457ffbdb9..0000000000 --- a/changelog.d/7281.wip +++ /dev/null @@ -1 +0,0 @@ -Links "Enable Notifications for this session" setting to enabled value in pusher diff --git a/changelog.d/7282.sdk b/changelog.d/7282.sdk deleted file mode 100644 index 14b71045cf..0000000000 --- a/changelog.d/7282.sdk +++ /dev/null @@ -1 +0,0 @@ -Stop using `original_event` field from `/relations` endpoint diff --git a/changelog.d/7283.wip b/changelog.d/7283.wip deleted file mode 100644 index f7cbd323f1..0000000000 --- a/changelog.d/7283.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Aggregate state events in the timeline diff --git a/changelog.d/7285.misc b/changelog.d/7285.misc deleted file mode 100644 index ce94383146..0000000000 --- a/changelog.d/7285.misc +++ /dev/null @@ -1 +0,0 @@ -Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. diff --git a/changelog.d/7288.feature b/changelog.d/7288.feature deleted file mode 100644 index be00e26179..0000000000 --- a/changelog.d/7288.feature +++ /dev/null @@ -1 +0,0 @@ -Add WYSIWYG editor. diff --git a/changelog.d/7288.sdk b/changelog.d/7288.sdk deleted file mode 100644 index 9c4a33ad22..0000000000 --- a/changelog.d/7288.sdk +++ /dev/null @@ -1,10 +0,0 @@ -Add `formattedText` or similar optional parameters in several methods: - -* RelationService: - * editTextMessage - * editReply - * replyToMessage -* SendService: - * sendQuotedTextMessage - -This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. diff --git a/changelog.d/7294.wip b/changelog.d/7294.wip deleted file mode 100644 index f163f6b680..0000000000 --- a/changelog.d/7294.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Render extended device info diff --git a/changelog.d/7300.wip b/changelog.d/7300.wip deleted file mode 100644 index 0a1777e651..0000000000 --- a/changelog.d/7300.wip +++ /dev/null @@ -1 +0,0 @@ -Implements client-side of local notification settings event diff --git a/changelog.d/7310.bugfix b/changelog.d/7310.bugfix deleted file mode 100644 index 3570b2d3ad..0000000000 --- a/changelog.d/7310.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Long session names not handled well diff --git a/changelog.d/7321.wip b/changelog.d/7321.wip deleted file mode 100644 index 2a539503b7..0000000000 --- a/changelog.d/7321.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Improve the parsing for OS of Desktop/Web sessions diff --git a/changelog.d/7324.wip b/changelog.d/7324.wip deleted file mode 100644 index 6602ef3c85..0000000000 --- a/changelog.d/7324.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Hide the IP address and last activity date on current session diff --git a/changelog.d/7327.wip b/changelog.d/7327.wip deleted file mode 100644 index 8f0191f948..0000000000 --- a/changelog.d/7327.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Update the unknown verification status icon diff --git a/changelog.d/7335.misc b/changelog.d/7335.misc deleted file mode 100644 index 3b14aa1339..0000000000 --- a/changelog.d/7335.misc +++ /dev/null @@ -1 +0,0 @@ -Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead. diff --git a/changelog.d/7336.feature b/changelog.d/7336.feature deleted file mode 100644 index fb2d165b57..0000000000 --- a/changelog.d/7336.feature +++ /dev/null @@ -1 +0,0 @@ -[Device management] Add lab flag for the feature diff --git a/changelog.d/7338.wip b/changelog.d/7338.wip deleted file mode 100644 index fc47ecb2f9..0000000000 --- a/changelog.d/7338.wip +++ /dev/null @@ -1 +0,0 @@ -Implement QR Code Login UI diff --git a/changelog.d/7344.feature b/changelog.d/7344.feature deleted file mode 100644 index a6deb4a23a..0000000000 --- a/changelog.d/7344.feature +++ /dev/null @@ -1 +0,0 @@ -[Device management] Add lab flag for matrix client info account data event diff --git a/changelog.d/7354.misc b/changelog.d/7354.misc deleted file mode 100644 index 0e146a8e02..0000000000 --- a/changelog.d/7354.misc +++ /dev/null @@ -1 +0,0 @@ -Update WYSIWYG editor designs. diff --git a/changelog.d/7358.sdk b/changelog.d/7358.sdk deleted file mode 100644 index 3d17076a44..0000000000 --- a/changelog.d/7358.sdk +++ /dev/null @@ -1 +0,0 @@ -Add support for `m.login.token` auth during QR code based sign in diff --git a/changelog.d/7359.bugfix b/changelog.d/7359.bugfix deleted file mode 100644 index 98e29fb697..0000000000 --- a/changelog.d/7359.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix editing formatted messages with plain text editor diff --git a/changelog.d/7359.sdk b/changelog.d/7359.sdk deleted file mode 100644 index c78c591d67..0000000000 --- a/changelog.d/7359.sdk +++ /dev/null @@ -1 +0,0 @@ -Allow getting the formatted or plain text body of a message for the fun `TimelineEvent.getTextEditableContent()`. diff --git a/changelog.d/7363.wip b/changelog.d/7363.wip deleted file mode 100644 index ee71e799fa..0000000000 --- a/changelog.d/7363.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Record and send non aggregated voice messages to the room diff --git a/changelog.d/7372.bugfix b/changelog.d/7372.bugfix deleted file mode 100644 index e63e00035b..0000000000 --- a/changelog.d/7372.bugfix +++ /dev/null @@ -1 +0,0 @@ -Handle properly when getUser returns null - prefer using getUserOrDefault diff --git a/changelog.d/7374.feature b/changelog.d/7374.feature deleted file mode 100644 index aa10696dca..0000000000 --- a/changelog.d/7374.feature +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Redirect to the new screen everywhere when lab flag is on diff --git a/changelog.d/7384.misc b/changelog.d/7384.misc deleted file mode 100644 index 3994dc0fa1..0000000000 --- a/changelog.d/7384.misc +++ /dev/null @@ -1 +0,0 @@ -Update WYSIWYG library to v0.2.1. diff --git a/changelog.d/7387.wip b/changelog.d/7387.wip deleted file mode 100644 index 881608829d..0000000000 --- a/changelog.d/7387.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Start listening to a voice broadcast diff --git a/changelog.d/7393.wip b/changelog.d/7393.wip deleted file mode 100644 index 7d82dc5769..0000000000 --- a/changelog.d/7393.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) diff --git a/changelog.d/7397.wip b/changelog.d/7397.wip deleted file mode 100644 index 1a7d1866a6..0000000000 --- a/changelog.d/7397.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Add additional data in events From e2a6a1942945612e22da7ffaae172566ee6bd6fc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 17:12:29 +0200 Subject: [PATCH 0223/1068] Rewrite the Changelog --- CHANGES.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 21d58026e5..7e2df7716b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,50 +3,50 @@ Changes in Element v1.5.4 (2022-10-19) Features ✨ ---------- - - Add WYSIWYG editor. ([#7288](https://github.com/vector-im/element-android/issues/7288)) - - [Device management] Add lab flag for the feature ([#7336](https://github.com/vector-im/element-android/issues/7336)) - - [Device management] Add lab flag for matrix client info account data event ([#7344](https://github.com/vector-im/element-android/issues/7344)) - - [Device Management] Redirect to the new screen everywhere when lab flag is on ([#7374](https://github.com/vector-im/element-android/issues/7374)) + - Add WYSIWYG editor, under a lab flag. ([#7288](https://github.com/vector-im/element-android/issues/7288)) + - New Device management, can be enabled in the labs settings. + - Voice broadcast can be enabled in the labs settings (recording is possible only on Android 10 and up). Bugfixes 🐛 ---------- - Fix wrong mic button direction to cancel on RTL languages ([#5968](https://github.com/vector-im/element-android/issues/5968)) + - Handle properly when getUser returns null - prefer using getUserOrDefault ([#7372](https://github.com/vector-im/element-android/issues/7372)) - [Device Management] Long session names not handled well ([#7310](https://github.com/vector-im/element-android/issues/7310)) - Fix editing formatted messages with plain text editor ([#7359](https://github.com/vector-im/element-android/issues/7359)) - - Handle properly when getUser returns null - prefer using getUserOrDefault ([#7372](https://github.com/vector-im/element-android/issues/7372)) In development 🚧 ---------------- - - Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API ([#7217](https://github.com/vector-im/element-android/issues/7217)) - [Device Management] Save "matrix_client_information" events on login/registration ([#7257](https://github.com/vector-im/element-android/issues/7257)) - - Adds pusher toggle setting to device manager v2 ([#7261](https://github.com/vector-im/element-android/issues/7261)) - - [Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget ([#7273](https://github.com/vector-im/element-android/issues/7273)) + - [Device management] Add lab flag for the feature ([#7336](https://github.com/vector-im/element-android/issues/7336)) + - [Device management] Add lab flag for matrix client info account data event ([#7344](https://github.com/vector-im/element-android/issues/7344)) + - [Device Management] Redirect to the new screen everywhere when lab flag is on ([#7374](https://github.com/vector-im/element-android/issues/7374)) - [Device Management] Show correct device type icons ([#7277](https://github.com/vector-im/element-android/issues/7277)) - - Links "Enable Notifications for this session" setting to enabled value in pusher ([#7281](https://github.com/vector-im/element-android/issues/7281)) - - [Voice Broadcast] Aggregate state events in the timeline ([#7283](https://github.com/vector-im/element-android/issues/7283)) - [Device Management] Render extended device info ([#7294](https://github.com/vector-im/element-android/issues/7294)) - - Implements client-side of local notification settings event ([#7300](https://github.com/vector-im/element-android/issues/7300)) - [Device management] Improve the parsing for OS of Desktop/Web sessions ([#7321](https://github.com/vector-im/element-android/issues/7321)) - [Device management] Hide the IP address and last activity date on current session ([#7324](https://github.com/vector-im/element-android/issues/7324)) - [Device management] Update the unknown verification status icon ([#7327](https://github.com/vector-im/element-android/issues/7327)) - - Implement QR Code Login UI ([#7338](https://github.com/vector-im/element-android/issues/7338)) + - [Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget ([#7273](https://github.com/vector-im/element-android/issues/7273)) + - [Voice Broadcast] Aggregate state events in the timeline ([#7283](https://github.com/vector-im/element-android/issues/7283)) - [Voice Broadcast] Record and send non aggregated voice messages to the room ([#7363](https://github.com/vector-im/element-android/issues/7363)) - [Voice Broadcast] Start listening to a voice broadcast ([#7387](https://github.com/vector-im/element-android/issues/7387)) - [Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) ([#7393](https://github.com/vector-im/element-android/issues/7393)) - [Voice Broadcast] Add additional data in events ([#7397](https://github.com/vector-im/element-android/issues/7397)) + - Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API ([#7217](https://github.com/vector-im/element-android/issues/7217)) + - Adds pusher toggle setting to device manager v2 ([#7261](https://github.com/vector-im/element-android/issues/7261)) + - Implement QR Code Login UI ([#7338](https://github.com/vector-im/element-android/issues/7338)) + - Implements client-side of local notification settings event ([#7300](https://github.com/vector-im/element-android/issues/7300)) + - Links "Enable Notifications for this session" setting to enabled value in pusher ([#7281](https://github.com/vector-im/element-android/issues/7281)) SDK API changes ⚠️ ------------------ - Stop using `original_event` field from `/relations` endpoint ([#7282](https://github.com/vector-im/element-android/issues/7282)) - Add `formattedText` or similar optional parameters in several methods: - * RelationService: * editTextMessage * editReply * replyToMessage * SendService: * sendQuotedTextMessage - This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. ([#7288](https://github.com/vector-im/element-android/issues/7288)) - Add support for `m.login.token` auth during QR code based sign in ([#7358](https://github.com/vector-im/element-android/issues/7358)) - Allow getting the formatted or plain text body of a message for the fun `TimelineEvent.getTextEditableContent()`. ([#7359](https://github.com/vector-im/element-android/issues/7359)) From 9d412ed6ad1689263f9b42e2c2a1864a760ad5c8 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Wed, 19 Oct 2022 12:48:37 +0000 Subject: [PATCH 0224/1068] Translated using Weblate (Czech) Currently translated at 100.0% (2505 of 2505 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 3410858988..398b7f7b35 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2859,4 +2859,6 @@ Pomocí tohoto zařízení se můžete přihlásit do mobilního nebo webového zařízení pomocí QR kódu. Můžete to provést dvěma způsoby: Přihlásit se pomocí QR kódu Naskenovat QR kód + Možnost nahrávat a odesílat hlasové vysílání na časové ose místnosti. + Povolit hlasové vysílání (v aktivním vývoji) \ No newline at end of file From 3598d27dfbdaf9bff2bc5e4e25c233e2a51668bc Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 19 Oct 2022 13:24:48 +0000 Subject: [PATCH 0225/1068] Translated using Weblate (German) Currently translated at 100.0% (2505 of 2505 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index fe849a1ddf..57adb82d3a 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2797,11 +2797,13 @@ Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code einzulesen: Mit QR-Code anmelden Benutze die Kamera auf diesem Gerät um den vom anderen Gerät angezeigten QR-Code zu scannen: - QR-Code scannen + QR-Code einlesen 3 2 1 Du kannst dieses Gerät benutzen um ein anderes Gerät per QR-Code anzumelden. Dafür gibt es zwei Wege: Mit QR-Code anmelden - QR-Code scannen + QR-Code einlesen + Zeichne Sprachnachrichten auf, während du sie in Echtzeit in den Raumverlauf sendest. + Sprachübertragung aktivieren (in aktiver Entwicklung) \ No newline at end of file From 54baf13b8299a0333d130e6e19703a40835a2067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 19 Oct 2022 13:42:54 +0000 Subject: [PATCH 0226/1068] Translated using Weblate (Estonian) Currently translated at 99.6% (2497 of 2505 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 7ead21394c..3ad6feec53 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2796,4 +2796,6 @@ ${app_name} vajab teavituste näitamiseks õigusi. Teavituste sisuks võivad olla sulle saadetud sõnumid, kutsed ja muud olulist. \n \nJärgmistes vaadetes palun anna sellele rakendusele teavituste kuvamiseks vajalikud õigused. + Võimalus salvestada ja postitada ringhäälingukõnesid jututoa ajajoonele. + Võta kasutusele ringhäälingukõned (aktiivses arenduses) \ No newline at end of file From 90e8028c5a61b3451cfa6d96a010354594916741 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 19 Oct 2022 12:36:10 +0000 Subject: [PATCH 0227/1068] Translated using Weblate (Italian) Currently translated at 100.0% (2505 of 2505 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- library/ui-strings/src/main/res/values-it/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index cea69030bc..be542cab88 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2796,4 +2796,6 @@ Puoi usare questo dispositivo per accedere in un dispositivo mobile o web con un codice QR. Ci sono due modi: Accedi con codice QR Scansiona codice QR + Registra e invia broadcast vocali nella linea temporale della stanza. + Attiva broadcast voce (in sviluppo attivo) \ No newline at end of file From 7daa2f7281c8bcefe2e9888a32424972ab08f5e2 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Wed, 19 Oct 2022 14:14:55 +0000 Subject: [PATCH 0228/1068] Translated using Weblate (Russian) Currently translated at 95.6% (2396 of 2505 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- library/ui-strings/src/main/res/values-ru/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 0349acedd1..e3edf65720 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -367,8 +367,8 @@ Добавить телефон Системные настройки приложения. Сведения о приложении - Включить уведомления для этой учетной записи - Включить уведомления для этой сессии + Уведомления для этой учётной записи + Уведомления для этой сессии В персональных чатах В групповых чатах Когда меня приглашают в комнату @@ -663,7 +663,7 @@ Включить Настройки сессии. Уведомления включены для этой сессии. - Уведомления не включено для этой сессии. + Уведомления не включены для этой сессии. \nПожалуйста, проверьте настройки ${app_name}. Включить Проверка сервисов Play @@ -1572,8 +1572,8 @@ или другой клиент Matrix поддерживающий перекрестную подпись Принудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату Чтобы продолжить, используйте %1$s или %2$s. - Используйте ключ восстановления - Выберите ключ восстановления или введите его вручную, введя или вставив из буфера обмена + Используйте бумажный ключ + Выберите бумажный ключ или введите его вручную, введя или вставив из буфера обмена Не удалось получить доступ к защищенному хранилищу данных Не зашифровано Зашифровано неподтверждённой сессией From 6b2ae76b48d3f65ebe07b1d3337f740d756ec46f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 17:14:07 +0200 Subject: [PATCH 0229/1068] Update recipe. --- .github/ISSUE_TEMPLATE/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index b28dbbde69..b41188a920 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -20,7 +20,6 @@ body: - [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores. - [ ] While Weblate is locked, and after the PR from Weblate has been merged, handle all the TODOs in the main `strings.xml` file - [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect. - - [ ] Ensure all [the required PRs](https://github.com/vector-im/element-android/pulls?q=is%3Aopen+is%3Apr+label%3AZ-NextRelease) have been merged ### Do the release @@ -32,7 +31,6 @@ body: - [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()` - [ ] Create an account on matrix.org and do some smoke tests that the sanity test does not cover like: 1-1 call, 1-1 video call, Jitsi call for instance - [ ] Run towncrier: `towncrier build --version v1.2.3 --draft` (remove `--draft` do write the file CHANGES.md) - - [ ] Check that the folder `changelog.d` is empty. It can happen that some remaining files stay here - [ ] Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things - [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs - [ ] (optional) Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes. From 86f70991276cff971d0553432085f3ed79d70c8a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 17:15:35 +0200 Subject: [PATCH 0230/1068] fastlane --- fastlane/metadata/android/en-US/changelogs/40105040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40105040.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40105040.txt b/fastlane/metadata/android/en-US/changelogs/40105040.txt new file mode 100644 index 0000000000..1073dc57e0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Main changes in this version: New features under the labs settings: Rich text composer, new device management, voice broadcast. Still under active development! +Full changelog: https://github.com/vector-im/element-android/releases From 36eb538a9348c5e6028954eb4cb7164f2639595d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 19 Oct 2022 17:17:21 +0200 Subject: [PATCH 0231/1068] Version++ --- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 4a6c0edf10..968d8515ac 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -62,7 +62,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.4\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.6\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index ca77e4b86f..1e8a8b3d3f 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 5 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 4 +ext.versionPatch = 6 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 399e3b1c5aaa10ccab0410da1e6cb21b11caed03 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 19 Oct 2022 18:29:04 +0000 Subject: [PATCH 0232/1068] Translated using Weblate (German) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../ui-strings/src/main/res/values-de/strings.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 57adb82d3a..10c0e262e3 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2784,9 +2784,9 @@ Beginne auf dem Anmeldebildschirm Wähle „Mit QR-Code anmelden“ Beginne auf dem Anmeldebildschirm - Wähle \'QR-Code auf diesem Gerät anzeigen\' - Gehe zu Einstellungen -> Sicherheit und Privatsphäre -> Alle Sitzungen anzeigen - Öffne ${app_name} auf deinem anderen Gerät + Wähle \'QR-Code anzeigen\' + Gehe zu Einstellungen -> Sicherheit und Privatsphäre + Öffne die App auf deinem anderen Gerät Die Anfrage wurde auf dem anderen Gerät abgelehnt. Die Verbindung konnte nicht in der erforderlichen Zeit hergestellt werden. Verbindung mit diesem Gerät nicht unterstützt. @@ -2806,4 +2806,11 @@ QR-Code einlesen Zeichne Sprachnachrichten auf, während du sie in Echtzeit in den Raumverlauf sendest. Sprachübertragung aktivieren (in aktiver Entwicklung) + Der Home-Server unterstützt Anmelden mit QR-Code nicht. + Die Anmeldung wurde vom anderen Gerät abgebrochen. + Der QR-Code ist ungültig. + Das andere Gerät muss angemeldet sein. + Das andere Gerät ist bereits angemeldet. + Es ist ein Problem bei der Herstellung der sicheren Kommunikation aufgetreten. Eines der folgenden Dinge könnte kompromitiert sein: Dein Heim-Server; deine Internetverbindung(en); dein(e) Gerät(e); + Die Anfrage ist fehlgeschlagen. \ No newline at end of file From 8b538a79bb3c6239da8666ef5815630b1ed7a761 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Wed, 19 Oct 2022 16:50:12 +0000 Subject: [PATCH 0233/1068] Translated using Weblate (Czech) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- .../src/main/res/values-cs/strings.xml | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 398b7f7b35..717da1624c 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -351,7 +351,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ů. + ${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 @@ -411,12 +411,12 @@ Hotovo Opravdu se chcete odhlásit\? Video hovor probíhá… - ${app_name} potřebuje oprávnění pro přístup k Vaší kameře a mikrofonu pro uskutečnění video hovoru. + ${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. + 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. Pokud administrátor serveru řekl, že toto je předpokládané, ujistěte se, že otisk níže se shoduje s otiskem který Vám poskytl. 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. @@ -578,7 +578,7 @@ Deaktivace účtu Deaktivovat můj účet Objevování - Správa Vašich nastavení pro 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. @@ -848,7 +848,7 @@ Začít používat zálohu klíčů (Pokročilé) Zabezpečit zálohu přístupovou frází. - Uložíme zašifrovanou kopii Vašich klíčů na Vašem domovském serveru. Chraňte svoji zálohu přístupovou frází, abyste ji udrželi v bezpečí. + Uložíme zašifrovanou kopii vašich klíčů na Vašem domovském serveru. Chraňte svoji zálohu přístupovou frází, abyste ji udrželi v bezpečí. \n \nZ důvodu nejvyšší bezpečnosti by se měla lišit od hesla účtu. Nastavit přístupovou frází @@ -856,7 +856,7 @@ Nebo zabezpečte svoji zálohu pomocí klíče obnovy, uloženého někde v bezpečí. (Pokročilé) Nastavit s klíčem obnovy Podařilo se! - Váš klíč obnovy je záchranná síť - lze jej použít pro obnovu Vašich šifrovaných zpráv, pokud zapomenete svou přístupovou frázi. + Váš klíč obnovy je záchranná síť - lze jej použít pro obnovu vašich šifrovaných zpráv, pokud zapomenete svou přístupovou frázi. \nUchovávejte svůj klíč obnovy velmi bezpečně, např. ve správci hesel (nebo trezoru) Uchovávejte svůj klíč obnovy velmi bezpečně, např. ve správci hesel (nebo trezoru) Hotovo @@ -1106,7 +1106,7 @@ Prémiový hosting pro organizace Zadejte adresu Modular Element nebo serveru, který chcete použít Při načítání stránky došlo k chybě: %1$s (%2$d) - Aplikace se nemůže přihlásit k tomuto homeserveru. Homeserver podporuje následující typy přihlášení: %1$s. + Aplikace se nemůže přihlásit k tomuto domovskému serveru. Domovský server podporuje následující typy přihlášení: %1$s. \n \nChcete se přihlásit webovým klientem\? Omlouváme se, tento server již nepřijímá nové účty. @@ -1279,7 +1279,7 @@ ${app_name} neobstarává události typu \'%1$s\' ${app_name} narazil na chybu při převádění obsahu události s id \'%1$s\' Odignorovat - Tato relace nemůže sdílet toto ověření s jinými z Vašich relací. + Tato relace nemůže sdílet toto ověření s jinými z vašich relací. \nToto ověření bude uloženo místně a sdíleno v budoucí verzi aplikace. Odešle danou zprávu zabarvenou jako duha Odešle daný emote zabarvený jako duha @@ -1475,7 +1475,7 @@ Manuálně ověřit textem Ověřit přihlášení Interaktivně ověřit pomocí Emoji - Potvrďte svou identitu ověřením tohoto přihlášení v některé z Vašich dalších relacích a udělte přístup k zašifrovaným zprávám. + Potvrďte svou identitu ověřením tohoto přihlášení v některé z vašich dalších relacích a udělte přístup k zašifrovaným zprávám. Zvolte si, prosím, uživatelské jméno. Prosím, zvolte heslo. Překontrolovat tento odkaz @@ -1517,7 +1517,7 @@ Nemáte povolení zahájit konferenční hovor v této místnosti Zahájit video schůzku Zahájit hlasovou schůzku - Schůzky používají pravidla zabezpečení a přístupu Jitsi. Všichni lidé nyní v místnosti uvidí pozvánku k připojení, zatímco Vaše schůzka probíhá. + Schůzky používají pravidla zabezpečení a přístupu Jitsi. Všichni lidé nyní v místnosti uvidí pozvánku k připojení, zatímco vaše schůzka probíhá. Nemůžete zahájit hovor se sebou Nemůžete zahájit hovor se sebou, počkejte, až účastníci přijmou pozvánku Přidání widgetu se nezdařilo @@ -1567,9 +1567,9 @@ Důvod k vykázání Zrušit vykázání uživatele Zrušení vykázání uživatele jim opět umožní vstoupit do místnosti. - Žádné telefonní číslo nebylo zadáno do Vašeho účtu + Žádné telefonní číslo nebylo zadáno do vašeho účtu Emailová adresa - Žádná emailová adresa nebyla zadána do Vašeho účtu + Žádná emailová adresa nebyla zadána do vašeho účtu Telefonní čísla Ostranit %s\? Ujistěte se, že kliknete na odkaz v e-mailu, který jsme Vám poslali. @@ -1577,7 +1577,7 @@ Vytvořit bezpečnou zálohu Resetovat bezpečnou zálohu Nastavit na tomto zařízení - Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na Vašem serveru. + Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na vašem serveru. Generovat nový bezpečnostní klíč nebo nastavit novou bezpečnostní frázi pro existující zálohu. To nahradí Váš nynější klíč nebo frázi. Integrace jsou vypnuty @@ -1641,7 +1641,7 @@ Zastavit fotoaparát Spustit fotoaparát Bezpečná záloha - Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na Vašem serveru. + Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na vašem serveru. Nastavit Použít bezpečnostní klíč Generovat bezpečnostní klíč k uložení na bezpečném místě např. správci hesel nebo sejfu. @@ -2104,7 +2104,7 @@ Prohlédnout a spravovat adresy tohoto prostoru. Adresy prostorů Aktualizujte na doporučenou verzi místnosti - Tato místnost používá místnost verze %s, kterou homeserver označil za nestabilní. + Tato místnost používá verzi místnosti %s, kterou domovský server označil za nestabilní. K aktualizaci místnosti potřebujete oprávnění Automaticky aktualizovat mateřský prostor Automaticky pozvat uživatele @@ -2124,7 +2124,7 @@ Raději ověřit porovnáním emoji Oskenovat tímto zařízením Oskenujte kód svým dalším zařízením nebo přepněte a oskenujte tímto zařízením - URL API Homeserveru + URL API domovského serveru Chybějící oprávnění Pro provedení této akce udělte, prosím, oprávnění Fotoaparát v systémových nastaveních. Některá z oprávnění potřebných k provedení akce chybí, prosím, udělte oprávnění v systémových nastaveních. @@ -2839,9 +2839,9 @@ Začněte na přihlašovací obrazovce Vyberte možnost \"Přihlásit se pomocí QR kódu\" Začněte na přihlašovací obrazovce - Vyberte možnost \"Zobrazit QR kód na tomto zařízení\" - Přejděte do Nastavení -> Zabezpečení a soukromí -> Zobrazit všechny relace - Otevřete ${app_name} na vašem druhém zařízení + Vyberte možnost \"Zobrazit QR kód\" + Přejděte do Nastavení -> Zabezpečení a soukromí + Otevřete aplikaci na vašem druhém zařízení Žádost byla na druhém zařízení zamítnuta. Propojení nebylo dokončeno v požadovaném čase. Propojení s tímto zařízením není podporováno. @@ -2861,4 +2861,11 @@ Naskenovat QR kód Možnost nahrávat a odesílat hlasové vysílání na časové ose místnosti. Povolit hlasové vysílání (v aktivním vývoji) + Domovský server nepodporuje přihlášení pomocí QR kódu. + Přihlášení bylo na druhém zařízení zrušeno. + Tento QR kód je neplatný. + Druhé zařízení musí být přihlášeno. + Druhé zařízení je již přihlášeno. + Při nastavování zabezpečeného zasílání zpráv se vyskytl problém se zabezpečením. Může být napadena jedna z následujících věcí: váš domovský server; vaše internetové připojení; vaše zařízení; + Žádost se nezdařila. \ No newline at end of file From a84123ff8bdffcfb10dd4a9d5a988f9656ea0629 Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 19 Oct 2022 18:38:09 +0000 Subject: [PATCH 0234/1068] Translated using Weblate (German) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 10c0e262e3..9e37fb513d 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2806,7 +2806,7 @@ QR-Code einlesen Zeichne Sprachnachrichten auf, während du sie in Echtzeit in den Raumverlauf sendest. Sprachübertragung aktivieren (in aktiver Entwicklung) - Der Home-Server unterstützt Anmelden mit QR-Code nicht. + Der Heim-Server unterstützt Anmelden per QR-Code nicht. Die Anmeldung wurde vom anderen Gerät abgebrochen. Der QR-Code ist ungültig. Das andere Gerät muss angemeldet sein. From fd105ae5aaef32be3e17eb4e2bbb2f73ae911b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 19 Oct 2022 17:54:08 +0000 Subject: [PATCH 0235/1068] Translated using Weblate (Estonian) Currently translated at 99.6% (2504 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- .../ui-strings/src/main/res/values-et/strings.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 3ad6feec53..05b01c2bde 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2754,9 +2754,9 @@ Alusta sisselogimisvaatest Vali „Logi võrku QR-koodi abil“ Alusta sisselogimisvaatest - Vali „Näita selles seadmes QR-koodi“ - Ava Seadistused -> Turvalisus ja privaatsus -> Näita kõiki sessioone - Ava ${app_name} oma teises seades + Vali „Näita QR-koodi“ + Ava Seadistused -> Turvalisus ja privaatsus + Ava sama rakendus oma teises seades Teine seade lükkas päringu tagasi. Sidumine ei lõppenud etteantud aja jooksul. Sidumine selle seadmega ei ole toetatud. @@ -2798,4 +2798,11 @@ \nJärgmistes vaadetes palun anna sellele rakendusele teavituste kuvamiseks vajalikud õigused. Võimalus salvestada ja postitada ringhäälingukõnesid jututoa ajajoonele. Võta kasutusele ringhäälingukõned (aktiivses arenduses) + Koduserver ei toeta muude seadmete võrku logimise võimalust. + Sisselogimine katkestati teises seadmes. + See QR-kood on vigane. + Teine seade peab olema võrku loginud. + Teine seade on juba võrku loginud. + Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade; + Päring ei õnnestunud. \ No newline at end of file From ad81aa764284940de8671819783726f8dacf7843 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Wed, 19 Oct 2022 16:38:14 +0000 Subject: [PATCH 0236/1068] Translated using Weblate (Russian) Currently translated at 95.3% (2396 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- library/ui-strings/src/main/res/values-ru/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index e3edf65720..77a555ca4e 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -830,8 +830,8 @@ Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её мнемонической фразой. \n \nДля максимальной безопасности мнемоническая фраза должна отличаться от пароля вашей учётной записи. - Ключ восстановления — это страховка, вы можете использовать его для восстановления доступа к вашим зашифрованным сообщениям, если забудете вашу парольную фразу. -\nХраните ключ восстановления в надёжном месте, например, в диспетчере паролей (или в сейфе) + Бумажный ключ — это подстраховка: вы можете использовать его для восстановления доступа к своим зашифрованным сообщениям, если забудете свою мнемоническую фразу. +\nХраните свой бумажный ключ в очень надёжном месте, например, в менеджере паролей (или в сейфе) Импортирование ключей… Скачивание ключей… Вычисление бумажного ключа… From 6e6bd6e4a2364d0d15dbb8944c977f3420095c5d Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 19 Oct 2022 16:48:00 +0000 Subject: [PATCH 0237/1068] Translated using Weblate (Slovak) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- .../ui-strings/src/main/res/values-sk/strings.xml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index 43a8301d58..289ab52a49 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2839,9 +2839,9 @@ Začnite na prihlasovacej obrazovke Vyberte možnosť \"Prihlásiť sa pomocou QR kódu\" Začnite na prihlasovacej obrazovke - Vyberte možnosť \"Zobraziť QR kód na tomto zariadení\" - Prejdite do Nastavenia -> Zabezpečenie a súkromie -> Zobraziť všetky relácie - Otvorte ${app_name} na vašom druhom zariadení + Vyberte možnosť \"Zobraziť QR kód\" + Prejdite do Nastavenia -> Zabezpečenie a súkromie + Otvorte aplikáciu na vašom druhom zariadení Žiadosť bola na druhom zariadení zamietnutá. Prepojenie nebolo dokončené v požadovanom čase. Prepojenie s týmto zariadením nie je podporované. @@ -2859,4 +2859,13 @@ Pomocou tohto zariadenia sa môžete prihlásiť do mobilného alebo webového zariadenia pomocou QR kódu. Môžete to urobiť dvoma spôsobmi: Prihlásiť sa pomocou QR kódu Skenovať QR kód + Domovský server nepodporuje prihlásenie pomocou QR kódu. + Prihlasovanie bolo zrušené na druhom zariadení. + QR kód nie je platný. + Druhé zariadenie musí byť prihlásené. + Druhé zariadenie je už prihlásené. + Pri nastavovaní zabezpečeného zasielania správ sa vyskytol bezpečnostný problém. Jedna z nasledujúcich možností môže byť kompromitovaná: Váš domovský server; Vaše internetové pripojenie (pripojenia); Vaše zariadenie (zariadenia); + Žiadosť zlyhala. + Možnosť nahrávania a odosielania hlasového vysielania v časovej osi miestnosti. + Zapnúť hlasové vysielanie (v štádiu aktívneho vývoja) \ No newline at end of file From 228a089de830638338bf5d8e733c404f92200926 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 19 Oct 2022 15:55:38 +0000 Subject: [PATCH 0238/1068] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- .../ui-strings/src/main/res/values-uk/strings.xml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 2a04e58f41..38e500e1fe 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2892,9 +2892,9 @@ Виберіть «Увійти за допомогою QR-коду» Почніть з екрана входу Почніть з екрана входу - Виберіть «Показати QR-код на цьому пристрої» - Перейдіть до Налаштування -> Безпека й приватність -> Показати всі сеанси - Відкрийте ${app_name} на іншому своєму пристрої + Виберіть «Показати QR-код» + Перейдіть до Налаштування -> Безпека й приватність + Відкрийте застосунок на іншому своєму пристрої Запит на іншому пристрої було відхилено. Пов\'язування не було завершено у встановлені терміни. Пов\'язування з цим пристроєм не підтримується. @@ -2913,4 +2913,13 @@ Сканувати QR-код Сканувати QR-код Сканувати QR-код + Домашній сервер не підтримує вхід за допомогою QR-коду. + Вхід на іншому пристрої було скасовано. + Цей QR-код недійсний. + Повинен бути виконаний вхід з іншого пристрою. + Вхід з іншого пристрою вже виконано. + Під час налаштування захищеного обміну повідомленнями виникла проблема з безпекою. Можливо, порушено одне з таких налаштувань: Ваш домашній сервер; Ваше інтернет-з\'єднання; Ваш пристрій; + Запит не виконаний. + Можливість записувати та надсилати голосові записи до стрічки кімнати. + Увімкнути голосове мовлення (в активній розробці) \ No newline at end of file From 1e3f8231ba165023e6e91d48c0f7fe0e36f9f291 Mon Sep 17 00:00:00 2001 From: jucktnich Date: Wed, 19 Oct 2022 18:48:20 +0000 Subject: [PATCH 0239/1068] Translated using Weblate (German) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 9e37fb513d..2d67b127f9 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2741,7 +2741,7 @@ ⚠ Es befinden sich nicht verifizierte Geräte in diesem Raum. Sie werden deine Nachrichten nicht entschlüsseln können. Niemals verschlüsselte Nachrichten zu unverifizierten Sitzungen in diesem Raum senden. Verstanden - Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus) + Probiere den Rich-Text-Editor aus (Klartext-Modus kommt bald) Aktiviere Rich-Text-Editor Browser Durchgestrichen formatieren From 5b1e29bb4f1beb0f3ce91888e4b5024fd32a32f6 Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 19 Oct 2022 18:42:29 +0000 Subject: [PATCH 0240/1068] Translated using Weblate (German) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 2d67b127f9..e8f720d1f5 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2811,6 +2811,6 @@ Der QR-Code ist ungültig. Das andere Gerät muss angemeldet sein. Das andere Gerät ist bereits angemeldet. - Es ist ein Problem bei der Herstellung der sicheren Kommunikation aufgetreten. Eines der folgenden Dinge könnte kompromitiert sein: Dein Heim-Server; deine Internetverbindung(en); dein(e) Gerät(e); + Es ist ein Problem bei der Herstellung der sicheren Kommunikation aufgetreten. Eines der folgenden Dinge könnte kompromittiert sein: Dein Heim-Server; deine Internetverbindung(en); dein(e) Gerät(e); Die Anfrage ist fehlgeschlagen. \ No newline at end of file From 2379ce673be7413fc2446429a8190a19e6ff5580 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Wed, 19 Oct 2022 17:02:32 -0400 Subject: [PATCH 0241/1068] Adds filter event to opt in to thread notifications --- .../sdk/api/session/sync/model/RoomSync.kt | 3 +- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo041.kt | 30 +++++++++++++++++++ .../query/TimelineEventEntityQueries.kt | 3 +- .../internal/session/filter/FilterFactory.kt | 17 ++--------- .../session/filter/RoomEventFilter.kt | 2 +- .../sdk/internal/session/sync/SyncTask.kt | 4 +-- 7 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt index 472121269f..0651b8f3d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt @@ -47,11 +47,10 @@ data class RoomSync( */ @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, - /** * The count of threads with unread notifications (not the total # of notifications in all threads) */ - @Json(name = "org.matrix.msc3773.unread_thread_notifications") val unreadThreadNotifications: Map? = null, + @Json(name = "unread_thread_notifications") val unreadThreadNotifications: Map? = null, /** * The room summary. 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 9a2c32f97c..5b2874bdef 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 @@ -56,6 +56,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -64,7 +65,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 39L, + schemaVersion = 41L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -113,5 +114,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 37) MigrateSessionTo037(realm).perform() if (oldVersion < 38) MigrateSessionTo038(realm).perform() if (oldVersion < 39) MigrateSessionTo039(realm).perform() + if (oldVersion < 41) MigrateSessionTo041(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt new file mode 100644 index 0000000000..b58d80e50a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt @@ -0,0 +1,30 @@ +/* + * 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 org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo041(realm: DynamicRealm) : RealmMigrator(realm, 41) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.THREAD_HIGHLIGHT_COUNT, Int::class.java) + ?.addField(RoomSummaryEntityFields.THREAD_NOTIFICATION_COUNT, Int::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 30010f90fd..1b4b359916 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -110,8 +110,7 @@ internal fun RealmQuery.filterEvents(filters: TimelineEvent endGroup() } if (filters.filterUseless) { - not() - .equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) + not().equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) } if (filters.filterEdits) { not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) 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 141144acd8..77c5649709 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 @@ -29,7 +29,6 @@ internal object FilterFactory { // senders = listOf(userId), // relationSenders = userId?.let { listOf(it) }, relationTypes = listOf(RelationType.THREAD), - enableUnreadThreadNotifications = true, ) } @@ -39,7 +38,6 @@ internal object FilterFactory { containsUrl = true, types = listOf(EventType.MESSAGE), lazyLoadMembers = true, - enableUnreadThreadNotifications = true, ) } @@ -57,32 +55,23 @@ internal object FilterFactory { } fun createDefaultRoomFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - ) + return RoomEventFilter(lazyLoadMembers = true) } fun createElementRoomFilter(): RoomEventFilter { return RoomEventFilter( lazyLoadMembers = true, - enableUnreadThreadNotifications = true, // TODO Enable this for optimization // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() ) } private fun createElementTimelineFilter(): RoomEventFilter? { - return null // RoomEventFilter().apply { - // TODO Enable this for optimization - // types = listOfSupportedEventTypes.toMutableList() - // } + return RoomEventFilter(enableUnreadThreadNotifications = true) } private fun createElementStateFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true, - enableUnreadThreadNotifications = true, - ) + return RoomEventFilter(lazyLoadMembers = true) } // Get only managed types by Element 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 81d1c04261..082acb63ab 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 @@ -81,7 +81,7 @@ internal data class RoomEventFilter( /** * If true, this will opt-in for the server to return unread threads notifications in [RoomSync] */ - @Json(name = "org.matrix.msc3773.unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null, + @Json(name = "unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null, ) { fun toJSONString(): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index ea296d379d..bc1a69769d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -140,7 +140,7 @@ internal class DefaultSyncTask @Inject constructor( executeRequest(globalErrorReceiver) { syncAPI.sync( params = requestParams, - readTimeOut = readTimeOut + readTimeOut = readTimeOut, ) } } @@ -178,7 +178,7 @@ internal class DefaultSyncTask @Inject constructor( syncRequestStateTracker.setSyncRequestState( SyncRequestState.IncrementalSyncParsing( rooms = nbRooms, - toDevice = nbToDevice + toDevice = nbToDevice, ) ) syncResponseHandler.handleResponse(syncResponse, token, null) From ad7a6bd76b2a6f6b9e7bf4e0228867b76aaa7462 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 23:07:12 +0000 Subject: [PATCH 0242/1068] Bump material from 1.6.1 to 1.7.0 Bumps [material](https://github.com/material-components/material-components-android) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/material-components/material-components-android/releases) - [Commits](https://github.com/material-components/material-components-android/compare/1.6.1...1.7.0) --- updated-dependencies: - dependency-name: com.google.android.material:material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f081e0a874..0d7ada0587 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -82,7 +82,7 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.6.1", + 'material' : "com.google.android.material:material:1.7.0", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", // Phone number https://github.com/google/libphonenumber From 26c550921af72d553575a98f7fbe095b2b4d6f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 08:27:20 +0200 Subject: [PATCH 0243/1068] Bump dependency-check-gradle from 7.2.1 to 7.3.0 (#7415) Bumps dependency-check-gradle from 7.2.1 to 7.3.0. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d0f093a451..f162685d7d 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.2.3" - classpath 'org.owasp:dependency-check-gradle:7.2.1' + classpath 'org.owasp:dependency-check-gradle:7.3.0' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' From 12da349316b46ffe56afce3f9204f8e28177fef2 Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 19 Oct 2022 20:03:24 +0000 Subject: [PATCH 0244/1068] Translated using Weblate (German) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index e8f720d1f5..cac0a47ad7 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -42,7 +42,7 @@ %s hat diesen Raum aufgewertet. Sende eine Nachricht … Erste Synchronisation: -\nImportiere Benutzerkonto … +\nImportiere Konto … Erste Synchronisation: \nImportiere Kryptoschlüssel Erste Synchronisation: @@ -263,13 +263,13 @@ Nur Matrix-Kontakte Keine Ergebnisse Räume - Logdateien übermitteln + Sende Protokolle Absturzberichte übermitteln Bildschirmfoto übermitteln Problem melden Bitte beschreibe das Problem. Was hast du genau gemacht\? Was sollte passieren\? Was ist tatsächlich passiert\? Problembeschreibung - Um Probleme diagnostizieren zu können, werden Protokolle der Anwendung zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, wie die Protokolle und das Bildschirmfoto, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, die nachfolgenden Haken entsprechend entfernen: + Um Probleme diagnostizieren zu können, werden Protokolle der Anwendung zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, inklusive der Protokolle und des Bildschirmfotos, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, entferne die Häkchen: Du scheinst dein Telefon frustriert zu schütteln. Möchtest du das Fenster zum Senden eines Fehlerberichts öffnen\? Dein Fehlerbericht wurde erfolgreich übermittelt Der Fehlerbericht konnte nicht übermittelt werden (%s) @@ -353,7 +353,7 @@ Telefonnummer hinzufügen Anwendungsinformationen in den Systemeinstellungen anzeigen. Anwendungsinformationen - Benachrichtigungen für diesen Account + Benachrichtigungen für dieses Konto Benachrichtigungen für diese Sitzung Direktnachrichten Gruppenunterhaltungen @@ -609,7 +609,7 @@ Schreibbenachrichtigungen senden Lasse andere Benutzer wissen, dass du tippst. Markdown-Formatierung - Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen (*), um kursiven Text anzuzeigen. + Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen, um kursiven Text anzuzeigen. Lesebestätigungen zeigen Klicke auf die Lesebestätigungen für eine detailliertere Liste. Einladungen, Entfernungen und Verbannungen bleiben sichtbar. @@ -637,7 +637,7 @@ \nBitte überprüfe die Systemeinstellungen. Öffne Einstellungen Kontoeinstellungen. - Benachrichtigungen sind für dein Konto eingeschaltet. + Benachrichtigungen sind für dein Konto aktiviert. Benachrichtigungen sind für dein Konto deaktiviert. \nBitte überprüfe die Kontoeinstellungen. Aktiviere @@ -686,7 +686,7 @@ Fertig Erweiterte Benachrichtigungseinstellungen Angepasste Einstellungen. - Beachte, dass einige Nachrichtentypen leise sind (erzeugen eine Benachrichtigung aber keinen Ton). + Beachte, dass einige Nachrichtentypen leise sind (erzeugen eine Benachrichtigung, aber keinen Ton). Einige Benachrichtigungen sind in deinen erweiterten Einstellungen deaktiviert. Konto hinzufügen Laute Benachrichtigungen einstellen @@ -721,7 +721,7 @@ \nDieser Fehler liegt nicht unter der Kontrolle von ${app_name}. Er kann aus verschiedenen Gründen auftreten. Vielleicht wird es funktionieren, wenn du es später noch einmal probierst. Außerdem kannst Du prüfen, ob die Datennutzung der Google-Play-Dienste unbeschränkt ist und die Geräteuhr richtig eingestellt ist. Der Fehler kann aber auch unter Custom-ROMs auftreten. [%1$s] \nDieser Fehler ist außerhalb von ${app_name} passiert. Es gibt kein Google-Konto auf dem Gerät. Bitte füge ein Google-Konto hinzu. - Verwaltung der Kryptoschlüssel + Verwaltung der Verschlüsselungs-Schlüssel Schlüsselsicherung verwalten 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 @@ -759,7 +759,7 @@ Um die Schlüsselsicherung für diese Sitzung zu verwenden, stelle sie jetzt mit deiner Passphrase oder deinem Wiederherstellungsschlüssel wieder her. Deine gesicherten Schlüssel vom Server löschen\? Du wirst deinen Wiederherstellungsschlüssel nicht mehr nutzen können, um deinen verschlüsselten Nachrichtenverlauf zu lesen. Beim Abmelden gehen deine verschlüsselten Nachrichten verloren - Schlüssel-Sicherung wird durchgeführt. Wenn du dich jetzt abmeldest, gehen deine verschlüsselten Nachrichten verloren. + Schlüsselsicherung läuft. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten. Schlüsselsicherung sollte bei allen Sitzungen aktiviert sein, um den Verlust verschlüsselter Nachrichten zu verhindern. Ich möchte meine verschlüsselten Nachrichten nicht Sichere Schlüssel … @@ -781,7 +781,7 @@ (Erweitert) Wiederherstellungsschlüssel einrichten Erfolg! Deine Schlüssel wurden gesichert. - Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz - du kannst es benutzen um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst. + Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz – du kannst es benutzen, um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst. \nVerwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) auf Ich habe eine Kopie angefertigt @@ -1024,7 +1024,7 @@ Es ist unangebracht Benutzerdefinierte Meldung … Diesen Inhalt melden - Meldegrund + Grund für Meldung des Inhalts MELDEN NUTZER IGNORIEREN Inhalt gemeldet @@ -1374,7 +1374,7 @@ Benachrichtigungskonfiguration Nachrichten mit \"@room\" Verschlüsselte Gruppenunterhaltungen - Sendet eine Nachricht als einfachen Text, ohne sie als Markdown zu interpretieren + Sendet eine Nachricht als Klartext, ohne sie als Markdown darzustellen Inkorrekter Benutzername und/oder Passwort. Das eingegebene Passwort beginnt oder endet mit Leerzeichen, bitte kontrolliere es. Nachrichtenschlüssel Wiederherstellungs-Passphrase @@ -1620,7 +1620,7 @@ Neue PIN Um deine PIN zurückzusetzen, musst du dich erneut anmelden und eine neue erstellen. Aktiviere PIN - Wenn du deine PIN zurücksetzen möchtest, tippe \"PIN vergessen\" um dich abzumelden und sie anschließend zurückzusetzen. + Wenn du deine PIN zurücksetzen möchtest, tippe auf „PIN vergessen“, um dich abzumelden und sie zurückzusetzen. Versehentliche Anrufe verhindern Bitte um Bestätigung, bevor du einen Anruf tätigst Einrichten @@ -1661,7 +1661,7 @@ E-Mail und Telefon Verwalte E-Mail-Adressen und Telefonnummern, die mit deinem Matrix-Konto verknüpft sind Code - Verwende das internationale Format (Telefonnummer muss mit \'+\' beginnen) + Bitte nutze das internationale Format (muss mit ‚+‘ beginnen) Bestätige deine Identität, indem du dieses Login verifizierst, um Zugriff auf verschlüsselte Nachrichten zu erhalten. Raum, indem du gebannt wurdest, kann nicht geöffnet werden. Raum kann nicht gefunden werden. Stelle sicher, dass er existiert. @@ -1688,7 +1688,7 @@ Details wie Raumnamen und Nachrichteninhalt zeigen. Inhalt in Benachrichtigungen anzeigen PIN-Code ist die einzige Möglichkeit ${app_name} zu entsperren. - Aktiviere Gerät-spezifische Biometrie wie Fingerabdrücke und Gesichtserkennung. + Aktiviere gerätespezifische Biometrie wie Fingerabdrücke und Gesichtserkennung. Biometrie aktivieren Schutz konfigurieren Zugriffsschutz @@ -1726,10 +1726,10 @@ Du siehst die Benachrichtigung! Klick mich! Benachrichtigungsanzeige Bei jedem Öffnen von ${app_name} ist der PIN-Code erforderlich. - PIN-Code ist erforderlich, nachdem ${app_name} 2 Minuten lang nicht verwendet wurde. - Fordere PIN nach 2 Minuten an + PIN-Code ist erforderlich, nachdem ${app_name} zwei Minuten lang nicht verwendet wurde. + Erfrage PIN nach zwei Minuten Nur die Anzahl ungelesener Nachrichten in der Benachrichtigung zeigen. - Bild hinzufügen mit + Füge Bild hinzu per Der Raum ist noch nicht erstellt. Raumerstellung abbrechen\? Zu niedrige Priorität hinzufügen Thema @@ -1742,7 +1742,7 @@ Raumname Prüfung exportieren Direktnachricht - Verlauf der Anfragen von Schlüsselfreigaben senden + Schlüsselfreigabe-Anfragen übermitteln Keine weiteren Ergebnisse Beginne eine Unterhaltung Autorisieren @@ -1762,7 +1762,7 @@ Änderungen daran, wer die Chronik lesen kann, gelten nur für kommende Nachrichten in diesem Raum. Die Sichtbarkeit der bestehenden Chronik bleibt unverändert. Zurückziehen Hinzufügen - Mit Nachricht teilen + Per Nachricht teilen Erweiterte Optionen ausblenden Erweiterte Optionen anzeigen Die Sichtbarkeit des Raums konnte nicht abgerufen werden (%1$s). @@ -1820,9 +1820,9 @@ Raumname ändern Sichtbarkeit des Verlaufs ändern Raum-Verschlüsselung aktivieren - Haupt-Adresse des Raums ändern + Hauptadresse des Raums ändern Raumbild ändern - Widgets verändern + Widgets ändern Jeden benachrichtigen Von anderen gesendete Nachrichten entfernen Nutzer verbannen @@ -1830,7 +1830,7 @@ Einstellungen ändern Nutzer einladen Nachrichten senden - Standard Rolle + Standard-Rolle Berechtigungen Berechtigungen Du hast nicht die Berechtigung zum Aktualisieren der Rollen, die zum Ändern verschiedener Teile des Raums erforderlich sind @@ -1918,9 +1918,9 @@ Die Obergrenze ist nicht bekannt. Dein Heim-Server akzeptiert Anhänge (wie Dateien, Medien, etc.) mit einer Größe bis zu %s. - Datei-Upload-Obergrenze des Servers + Dateigrößenlimit des Servers Serverversion - Servername + Server-Name Raumeinstellungen Derzeitige Konferenz verlassen und zu einer anderen wechseln\? Raum-Version @@ -1957,8 +1957,8 @@ Spaces Jeder kann im Raum anklopfen, Mitglieder können dann zustimmen oder ablehnen Momentan bist nur du hier. Mit anderen Leuten wird %s noch viel besser. - Diese werden in der Lage sein, %s zu durchsuchen - Diese werden kein Teil von %s sein + Sie wird in der Lage sein, %s zu durchsuchen + Sie wird kein Teil von %s sein Tritt meinem Space %1$s %2$s bei Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Räume oder Spaces hinzufügen @@ -1979,7 +1979,7 @@ Space beitreten Space erstellen Nur zu diesem Raum - In Space \"%s\" einladen + Zu %s einladen Link teilen Mithilfe einer E-Mail-Adresse einladen Personen einladen @@ -2067,7 +2067,7 @@ Beim Versuch %s beizutreten, ist leider ein Fehler aufgetreten Zur empfohlenen Raumversion upgraden Ersatzraum betreten - Raum zu neuer Version upgraden + Aktualisiert den Raum auf eine neue Version stabil instabil Raumversionen 👓 @@ -2108,7 +2108,7 @@ Spaces wählen Mitglieder von %s können Räume finden, betrachten und betreten. Privat (Zutritt nur mit Einladung) - Raumupgrades + Raumaktualisierungen Nachrichten von Bots Raumeinladungen Verschlüsselte Gruppennachrichten @@ -2192,9 +2192,9 @@ Neuen Space erstellen Zugriff Wer hat Zugriff\? - Benachrichtigungen per Email für %s aktivieren + Benachrichtigungen per E-Mail für %s aktivieren Um Benachrichtigungen per E-Mail zu empfangen, musst du eine E-Mail-Adresse hinzufügen - Emailbenachrichtigungen + E-Mail-Benachrichtigungen Space upgraden Namen vom Space ändern Space verschlüsseln @@ -2224,7 +2224,7 @@ Blockiert eine Person und versteckt deren Nachrichten Jeder kann den Space finden und beitreten Du kannst deine Benachrichtigungen in den %1$s verwalten. - Beachte, dass Benachrichtigungen zu Erwähnungen und Schlüsselwörtern in verschlüsselten Räumen momentan nicht verfügbar sind. + Bitte beachte, dass Benachrichtigungen zu Erwähnungen und Schlüsselwörtern in verschlüsselten Räumen mobil nicht verfügbar sind. Wähle die Berechtigungen der Rollen aus Rollen deren Berechtigungen einsehen und bearbeiten. @@ -2253,7 +2253,7 @@ Externe Bibliotheken Du kannst dies jederzeit in den Einstellungen deaktivieren Wir teilen keine Informationen mit Drittpersonen - Wir erfassen und analysieren keine Accountdaten + Wir erfassen und analysieren keine Kontodaten Hilf uns dabei Probleme zu identifizieren und ${app_name} zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird. \n \n%s kannst du alle unsere Bedingungen lesen. @@ -2269,7 +2269,7 @@ Hilfe Rechtliches Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen betreten. - hier + Hier Hilf mit, ${app_name} zu verbessern Aktivieren Farbe des Anzeigenamens ändern @@ -2741,8 +2741,8 @@ ⚠ Es befinden sich nicht verifizierte Geräte in diesem Raum. Sie werden deine Nachrichten nicht entschlüsseln können. Niemals verschlüsselte Nachrichten zu unverifizierten Sitzungen in diesem Raum senden. Verstanden - Probiere den Rich-Text-Editor aus (Klartext-Modus kommt bald) - Aktiviere Rich-Text-Editor + Probiere den Textverarbeitungs-Editor (bald auch mit Klartext-Modus) + Textverarbeitungs-Editor aktivieren Browser Durchgestrichen formatieren Kursiv formatieren From 5f90f135aebd3a33f5fbfc68d2a7ec6fcd630104 Mon Sep 17 00:00:00 2001 From: Glandos Date: Wed, 19 Oct 2022 20:42:29 +0000 Subject: [PATCH 0245/1068] Translated using Weblate (French) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- .../src/main/res/values-fr/strings.xml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index b77173519d..67396a390f 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2772,4 +2772,46 @@ \nVeuillez autoriser l’accès sur la prochaine fenêtre pour pouvoir voir des notifications. Essayer l’éditeur de texte formaté (le mode texte brut arrive bientôt) Activer l’éditeur de texte formaté + Vérifiez l’origine de ce code. En appairant un appareil, vous lui fournissez un accès complet à votre compte. + Confirmer + Réessayez + Pas de correspondance \? + Connexion + Connexion à l’appareil + Scanner le QR code + Connexion sur un appareil mobile \? + Afficher le QR code sur cet appareil + Sélectionnez « Scanner le QR code » + Démarrez à l’écran de connexion + Sélectionnez « Se connecter avec un QR code » + Démarrez à l’écran de connexion + Sélectionnez « Afficher le QR code » + Allez dans Réglages -> Confidentialité et sécurité + Ouvrez l’application sur votre autre appareil + Le serveur d’accueil ne prend pas en charge la connexion avec un QR code. + La connexion a été annulée sur l’autre appareil. + Ce QR code est invalide. + L’autre appareil doit être connecté. + L’autre appareil est déjà connecté. + La configuration de la messagerie sécurisée a rencontré un problème de sécurité. Un des éléments suivants pourrait être compromis : votre serveur d’accueil ; votre connexion Internet ; votre (vos) appareil(s) ; + La requête a échoué. + La requête a été refusée sur l’autre appareil. + L’appairage n’a pas été effectué dans le temps imparti. + L’appairage avec cet appareil n’est pas pris en charge. + Échec de la connexion + Vérifiez votre appareil connecté, le code ci-dessous devrait y être affiché. Confirmez que le code ci-dessous correspond à celui de l’autre appareil : + Connexion sécurisée établie + Scannez le QR code ci-dessous avec l’appareil qui n’est pas connecté. + Utilisez votre appareil connecté pour scanner le QR code ci-dessous : + Se connecter avec un QR code + Utilisez l’appareil photo de cet appareil pour scanner le QR code affiché sur votre autre appareil : + Scanner le QR code + 3 + 2 + 1 + Pouvoir enregistrer et envoyer une diffusion audio dans l’historique du salon. + Activer la diffusion audio (en cours de développement) + Vous pouvez utiliser cet appareil pour connecter un appareil mobile ou un client web avec un QR code. Il y a deux façons de le faire : + Se connecter avec un QR code + Scanner le QR code \ No newline at end of file From 7796d97a98fa9de8fde2172bfe6b22eb437dbe94 Mon Sep 17 00:00:00 2001 From: phardyle Date: Thu, 20 Oct 2022 02:49:51 +0000 Subject: [PATCH 0246/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.5% (2376 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index ae29132c91..f1fe3a496f 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -2625,4 +2625,11 @@ 简化的Element,带有可选的标签 无痕键盘 要求键盘不要基于你在对话中的输入更新任何个性化数据,如输入历史和字典。请注意,某些键盘可能不会遵守此设置。 + ${app_name}需要权限来显示通知。通知可以显示消息、邀请等。 +\n +\n请在下个弹窗允许访问以便查看通知。 + 试用富文本编辑器(纯文本模式即将到来) + 启用富文本编辑器 + 折叠%s孩子 + 展开%s孩子 \ No newline at end of file From e0978a2fc3d5115b1638d1013ae7444f69c07af3 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 20 Oct 2022 01:42:46 +0000 Subject: [PATCH 0247/1068] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2512 of 2512 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- .../src/main/res/values-zh-rTW/strings.xml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index c9057cd289..d1f81567c2 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2731,9 +2731,9 @@ 從登入畫面開始 選取「使用 QR code 登入」 從登入畫面開始 - 選取「在此裝置上顯示 QR code」 - 到「設定」→「安全與隱私」→「顯示所有工作階段」 - 在您的其他裝置上開啟 ${app_name} + 選取「顯示 QR code」 + 到「設定」→「安全與隱私」 + 在您的其他裝置上開啟應用程式 請求在另一台裝置上被拒絕。 連結未在規定時間內完成。 不支援與其裝置連結。 @@ -2751,4 +2751,13 @@ 您可以使用此裝置透過 QR code 登入移動裝置或網路裝置。有兩種方法可以作到: 使用 QR code 登入 掃描 QR code + 家伺服器不支援使用 QR code 登入。 + 登入已在其他裝置上取消。 + 該 QR code 無效。 + 其他裝置必須登入。 + 其他裝置已登入。 + 設定安全訊息傳遞時遇到安全問題。以下其中一項可能已被駭入:您的家伺服器、您的網際網路連線、您的裝置; + 請求失敗。 + 可以在聊天室時間軸中錄製並傳送語音廣播。 + 啟用語音廣播(正在積極開發中) \ No newline at end of file From a5ad00a06532bff235c163d37c3c89e207bf5181 Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 19 Oct 2022 19:02:49 +0000 Subject: [PATCH 0248/1068] Translated using Weblate (German) Currently translated at 100.0% (79 of 79 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/40105000.txt | 2 +- fastlane/metadata/android/de-DE/changelogs/40105020.txt | 2 +- fastlane/metadata/android/de-DE/changelogs/40105040.txt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105040.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40105000.txt b/fastlane/metadata/android/de-DE/changelogs/40105000.txt index cd3ec93387..254c0fe0d8 100644 --- a/fastlane/metadata/android/de-DE/changelogs/40105000.txt +++ b/fastlane/metadata/android/de-DE/changelogs/40105000.txt @@ -1,2 +1,2 @@ Die wichtigste Änderung in dieser Version: Verzögerte Direktnachrichten standardmäßig aktiviert! -Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.2.0 +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105020.txt b/fastlane/metadata/android/de-DE/changelogs/40105020.txt index ac08e662db..af7a8d7cce 100644 --- a/fastlane/metadata/android/de-DE/changelogs/40105020.txt +++ b/fastlane/metadata/android/de-DE/changelogs/40105020.txt @@ -1,2 +1,2 @@ Die wichtigste Änderung in dieser Version: Neues App-Layout standardmäßig aktiviert! -Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.2.0 +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105040.txt b/fastlane/metadata/android/de-DE/changelogs/40105040.txt new file mode 100644 index 0000000000..017e23cd9e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Neue Funktionen in den Labor-Einstellungen: Textverarbeitungs-Editor, neue Geräteverwaltung, Sprachübertragung. Noch in aktiver Entwicklung! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases From dc6722ec226e6d8101fe6e03236f06547a8368f4 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Wed, 19 Oct 2022 20:22:04 +0000 Subject: [PATCH 0249/1068] Translated using Weblate (Russian) Currently translated at 88.6% (70 of 79 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru-RU/changelogs/40105020.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40105040.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105020.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105040.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105020.txt b/fastlane/metadata/android/ru-RU/changelogs/40105020.txt new file mode 100644 index 0000000000..83bf3c747b --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: новый вид приложения включён по умолчанию! +Весь список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105040.txt b/fastlane/metadata/android/ru-RU/changelogs/40105040.txt new file mode 100644 index 0000000000..c923750bc4 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии — новые возможности в настройках лаборатории: наглядный текстовый редактор, новое управление устройствами, голосовая трансляция. Всё это ещё находится в активной разработке! +Весь список изменений: https://github.com/vector-im/element-android/releases From 1b8308c12bd2eda4d7010509f5a7f3f02242be66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 19 Oct 2022 18:40:32 +0000 Subject: [PATCH 0250/1068] Translated using Weblate (Estonian) Currently translated at 100.0% (79 of 79 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40105040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40105040.txt diff --git a/fastlane/metadata/android/et/changelogs/40105040.txt b/fastlane/metadata/android/et/changelogs/40105040.txt new file mode 100644 index 0000000000..b1c84cad47 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Uued võimalused katsete all: vormindatud teksti põhine toimeti, uus seadmehaldus, ringhäälingukõned (kõik on hetkel aktiivsel arendamisel). +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases From b95ae7d36a2767d7363d92098f8f82cdb4f38ee2 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 20 Oct 2022 01:44:30 +0000 Subject: [PATCH 0251/1068] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (79 of 79 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40105040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105040.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105040.txt b/fastlane/metadata/android/zh-TW/changelogs/40105040.txt new file mode 100644 index 0000000000..b35b1185b9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105040.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:實驗室設定下有新功能:格式化文字編輯器、新裝置管理、語音廣播。仍在積極開發中! +完整的變更紀錄:https://github.com/vector-im/element-android/releases From 08271218e6cc2c50a28f2253fe372dbee5c9d3b2 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Wed, 19 Oct 2022 18:43:55 +0000 Subject: [PATCH 0252/1068] Translated using Weblate (Czech) Currently translated at 100.0% (79 of 79 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40105040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105040.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt new file mode 100644 index 0000000000..c1bf4fd59a --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Nové funkce v Experimentálních funkcích: Rozšířený editor zpráv, nová správa zařízení, hlasové vysílání. Stále v aktivním vývoji! +Úplný seznam změn: https://github.com/vector-im/element-android/releases From 6554f571f22a45212ee926c55b2da93d63716826 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 11:18:35 +0200 Subject: [PATCH 0253/1068] VoiceBroadcastPlayer - Inject ActiveSessionHolder instead of Session --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index dfd50ea5cb..e93e128686 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast import android.media.AudioAttributes import android.media.MediaPlayer +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voice.VoiceFailure @@ -25,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom @@ -39,10 +39,12 @@ import javax.inject.Singleton @Singleton class VoiceBroadcastPlayer @Inject constructor( - private val session: Session, + private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, ) { + private val session get() = sessionHolder.getActiveSession() + private val mediaPlayerScope = CoroutineScope(Dispatchers.IO) private var currentMediaPlayer: MediaPlayer? = null From b89ab6c2fd54cedfb76228d3189deafbecee705a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:04:03 +0200 Subject: [PATCH 0254/1068] VoiceBroadcastPlayer - release previous MediaPlayer --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index e93e128686..c7259b12f8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -76,10 +76,8 @@ class VoiceBroadcastPlayer @Inject constructor( fun stop() { currentMediaPlayer?.stop() - currentMediaPlayer?.release() - currentMediaPlayer?.setOnInfoListener(null) - currentMediaPlayer = null currentVoiceBroadcastEventId?.let { playbackTracker.stopPlayback(it) } + release(currentMediaPlayer) playlist = emptyList() currentPlayingIndex = -1 } @@ -147,11 +145,21 @@ class VoiceBroadcastPlayer @Inject constructor( } } + private fun release(mp: MediaPlayer?) { + mp?.apply { + release() + setOnInfoListener(null) + setOnCompletionListener(null) + setOnErrorListener(null) + } + } + inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { + release(currentMediaPlayer) currentMediaPlayer = mp currentPlayingIndex++ mediaPlayerScope.launch { prepareNextFile() } From 0c847cffc131f446cc94c3757883aee2ea870f21 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:16:54 +0200 Subject: [PATCH 0255/1068] VoiceBroadcastPlayer - Use more accurate coroutine scope --- .../features/voicebroadcast/VoiceBroadcastPlayer.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index c7259b12f8..db07503927 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlayb import im.vector.app.features.voice.VoiceFailure import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.RelationType @@ -42,13 +43,13 @@ class VoiceBroadcastPlayer @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, ) { - - private val session get() = sessionHolder.getActiveSession() - - private val mediaPlayerScope = CoroutineScope(Dispatchers.IO) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val session + get() = sessionHolder.getActiveSession() private var currentMediaPlayer: MediaPlayer? = null private var currentPlayingIndex: Int = -1 + private var playlist = emptyList() private val currentVoiceBroadcastEventId get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId @@ -90,7 +91,7 @@ class VoiceBroadcastPlayer @Inject constructor( private fun startPlayback() { val content = playlist.firstOrNull()?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } - mediaPlayerScope.launch { + coroutineScope.launch { try { currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() @@ -162,7 +163,7 @@ class VoiceBroadcastPlayer @Inject constructor( release(currentMediaPlayer) currentMediaPlayer = mp currentPlayingIndex++ - mediaPlayerScope.launch { prepareNextFile() } + coroutineScope.launch { prepareNextFile() } } } return false From fe44a829afdc23d35e0fc13a399affe77d19c7b6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:17:59 +0200 Subject: [PATCH 0256/1068] VoiceBroadcastPlayer - Improve currentVoiceBroadcastId --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index db07503927..72ec181966 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -51,8 +51,8 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentPlayingIndex: Int = -1 private var playlist = emptyList() - private val currentVoiceBroadcastEventId - get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId + private val currentVoiceBroadcastId + get() = playlist.getOrNull(currentPlayingIndex)?.root?.getRelationContent()?.eventId private val mediaPlayerListener = MediaPlayerListener() @@ -60,7 +60,7 @@ class VoiceBroadcastPlayer @Inject constructor( val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") when { - currentVoiceBroadcastEventId != eventId -> { + currentVoiceBroadcastId != eventId -> { stop() updatePlaylist(room, eventId) startPlayback() @@ -72,12 +72,12 @@ class VoiceBroadcastPlayer @Inject constructor( fun pause() { currentMediaPlayer?.pause() - currentVoiceBroadcastEventId?.let { playbackTracker.pausePlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) } } fun stop() { currentMediaPlayer?.stop() - currentVoiceBroadcastEventId?.let { playbackTracker.stopPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) } release(currentMediaPlayer) playlist = emptyList() currentPlayingIndex = -1 @@ -96,7 +96,7 @@ class VoiceBroadcastPlayer @Inject constructor( currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() currentPlayingIndex = 0 - currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } prepareNextFile() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") @@ -107,7 +107,7 @@ class VoiceBroadcastPlayer @Inject constructor( private fun resumePlayback() { currentMediaPlayer?.start() - currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } } private suspend fun prepareNextFile() { From e9c81ca98fea45ced23afa32185d3a29762ded4a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 18:07:44 +0200 Subject: [PATCH 0257/1068] VoiceBroadcastPlayer - Live listening --- .../sdk/api/session/events/model/Event.kt | 2 +- .../room/timeline/TimelineEventDataSource.kt | 10 +- .../voicebroadcast/VoiceBroadcastHelper.kt | 2 +- .../voicebroadcast/VoiceBroadcastPlayer.kt | 236 +++++++++++++++--- .../usecase/GetVoiceBroadcastStateUseCase.kt | 41 +++ 5 files changed, 249 insertions(+), 42 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt 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 71daf4cc4f..1f16041b54 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 @@ -401,7 +401,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { when (getClearType()) { EventType.STICKER -> getClearContent().toModel()?.relatesTo in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel()?.relatesTo - else -> null + else -> getClearContent()?.get("m.relates_to")?.toContent().toModel() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt index 20094e4be8..2d6082f9b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt @@ -22,6 +22,8 @@ import io.realm.Sort import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider @@ -74,7 +76,13 @@ internal class TimelineEventDataSource @Inject constructor( .distinct(TimelineEventEntityFields.EVENT_ID) .findAll() .mapNotNull { - timelineEventMapper.map(it).takeIf { it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null } + timelineEventMapper.map(it) + .takeIf { + val isEventRelatedTo = it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null + val isContentRelatedTo = it.root.getClearContent()?.toModel() + ?.relatesTo?.takeIf { it.type == eventType && it.eventId == eventId } != null + isEventRelatedTo || isContentRelatedTo + } } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index b967afa9cb..58e7de7f32 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -40,7 +40,7 @@ class VoiceBroadcastHelper @Inject constructor( suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId) - fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.play(roomId, eventId) + fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.playOrResume(roomId, eventId) fun pausePlayback() = voiceBroadcastPlayer.pause() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 72ec181966..7f5e13504e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -20,13 +20,19 @@ import android.media.AudioAttributes import android.media.MediaPlayer import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voice.VoiceFailure +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom @@ -34,6 +40,11 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +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.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -42,62 +53,117 @@ import javax.inject.Singleton class VoiceBroadcastPlayer @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, + private val getVoiceBroadcastStateUseCase: GetVoiceBroadcastStateUseCase, ) { - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val session get() = sessionHolder.getActiveSession() + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private var voiceBroadcastStateJob: Job? = null + private var currentTimeline: Timeline? = null + set(value) { + field?.removeAllListeners() + field?.dispose() + field = value + } + + private val mediaPlayerListener = MediaPlayerListener() + private var timelineListener: TimelineListener? = null + private var currentMediaPlayer: MediaPlayer? = null - private var currentPlayingIndex: Int = -1 + private var nextMediaPlayer: MediaPlayer? = null + set(value) { + field = value + currentMediaPlayer?.setNextMediaPlayer(value) + } + private var currentSequence: Int? = null private var playlist = emptyList() private val currentVoiceBroadcastId - get() = playlist.getOrNull(currentPlayingIndex)?.root?.getRelationContent()?.eventId + get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId - private val mediaPlayerListener = MediaPlayerListener() - - fun play(roomId: String, eventId: String) { - val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + private var state: State = State.IDLE + set(value) { + Timber.w("## VoiceBroadcastPlayer state: $field -> $value") + field = value + } + fun playOrResume(roomId: String, eventId: String) { + val hasChanged = currentVoiceBroadcastId != eventId when { - currentVoiceBroadcastId != eventId -> { - stop() - updatePlaylist(room, eventId) - startPlayback() - } - playbackTracker.getPlaybackState(eventId) is State.Playing -> pause() - else -> resumePlayback() + hasChanged -> startPlayback(roomId, eventId) + state == State.PAUSED -> resumePlayback() + else -> Unit } } fun pause() { currentMediaPlayer?.pause() currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) } + state = State.PAUSED } fun stop() { + // Stop playback currentMediaPlayer?.stop() currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) } + + // Release current player release(currentMediaPlayer) + currentMediaPlayer = null + + // Release next player + release(nextMediaPlayer) + nextMediaPlayer = null + + // Do not observe anymore voice broadcast state changes + voiceBroadcastStateJob?.cancel() + voiceBroadcastStateJob = null + + // In case of live broadcast, stop observing new chunks + currentTimeline?.dispose() + currentTimeline?.removeAllListeners() + currentTimeline = null + timelineListener = null + + // Update state + state = State.IDLE + + // Clear playlist playlist = emptyList() - currentPlayingIndex = -1 + currentSequence = null } - private fun updatePlaylist(room: Room, eventId: String) { - val timelineEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) - val audioEvents = timelineEvents.mapNotNull { it.root.asMessageAudioEvent() } - playlist = audioEvents.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs } + private fun startPlayback(roomId: String, eventId: String) { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + // Stop listening previous voice broadcast if any + if (state != State.IDLE) stop() + + state = State.BUFFERING + + val voiceBroadcastState = getVoiceBroadcastStateUseCase.execute(roomId, eventId) + if (voiceBroadcastState == VoiceBroadcastState.STOPPED) { + // Get static playlist + updatePlaylist(getExistingChunks(room, eventId)) + startPlayback(false) + } else { + playLiveVoiceBroadcast(room, eventId) + } } - private fun startPlayback() { - val content = playlist.firstOrNull()?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } + private fun startPlayback(isLive: Boolean) { + val event = if (isLive) playlist.lastOrNull() else playlist.firstOrNull() + val content = event?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } + val sequence = event.getVoiceBroadcastChunk()?.sequence coroutineScope.launch { try { currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() - currentPlayingIndex = 0 currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } - prepareNextFile() + currentSequence = sequence + state = State.PLAYING + nextMediaPlayer = prepareNextMediaPlayer() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") throw VoiceFailure.UnableToPlay(failure) @@ -105,19 +171,68 @@ class VoiceBroadcastPlayer @Inject constructor( } } + private fun playLiveVoiceBroadcast(room: Room, eventId: String) { + val voiceBroadcastEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() + ?: error("Cannot retrieve voice broadcast $eventId") + updatePlaylist(getExistingChunks(room, eventId)) + startPlayback(true) + room.flow() + .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!)) + .unwrap() + .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .onEach { state -> + when (state) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED -> { + observeIncomingChunks(room, eventId) + } + VoiceBroadcastState.STOPPED -> { + currentTimeline?.dispose() + currentTimeline?.removeAllListeners() + currentTimeline = null + } + } + } + .launchIn(coroutineScope) + } + + private fun getExistingChunks(room: Room, eventId: String): List { + return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) + .mapNotNull { it.root.asMessageAudioEvent() } + .filter { it.isVoiceBroadcast() } + } + + private fun observeIncomingChunks(room: Room, eventId: String) { + // Fixme this is probably not necessary here + currentTimeline?.dispose() + currentTimeline?.removeAllListeners() + currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline -> + timelineListener = TimelineListener(eventId).also { timeline.addListener(it) } + timeline.start() + } + } + private fun resumePlayback() { currentMediaPlayer?.start() currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } + state = State.PLAYING } - private suspend fun prepareNextFile() { - val nextContent = playlist.getOrNull(currentPlayingIndex + 1)?.content - if (nextContent == null) { - currentMediaPlayer?.setOnCompletionListener(mediaPlayerListener) - } else { - val nextMediaPlayer = prepareMediaPlayer(nextContent) - currentMediaPlayer?.setNextMediaPlayer(nextMediaPlayer) - } + private fun updatePlaylist(playlist: List) { + this.playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs } + } + + private fun getNextAudioContent(): MessageAudioContent? { + val nextSequence = currentSequence?.plus(1) + ?: timelineListener?.let { playlist.lastOrNull()?.sequence } + ?: 1 + return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content + } + + private suspend fun prepareNextMediaPlayer(): MediaPlayer? { + val nextContent = getNextAudioContent() ?: return null + return prepareMediaPlayer(nextContent) } private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { @@ -141,6 +256,7 @@ class VoiceBroadcastPlayer @Inject constructor( setDataSource(fis.fd) setOnInfoListener(mediaPlayerListener) setOnErrorListener(mediaPlayerListener) + setOnCompletionListener(mediaPlayerListener) prepare() } } @@ -155,24 +271,59 @@ class VoiceBroadcastPlayer @Inject constructor( } } - inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { + private inner class TimelineListener(private val voiceBroadcastId: String) : Timeline.Listener { + override fun onTimelineUpdated(snapshot: List) { + val currentSequences = playlist.map { it.sequence } + val newChunks = snapshot + .mapNotNull { timelineEvent -> + timelineEvent.root.asMessageAudioEvent() + ?.takeIf { it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.sequence !in currentSequences } + } + if (newChunks.isEmpty()) return + updatePlaylist(playlist + newChunks) + + when (state) { + State.PLAYING -> { + if (nextMediaPlayer == null) { + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + } + } + State.PAUSED -> { + if (nextMediaPlayer == null) { + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + } + } + State.BUFFERING -> { + val newMediaContent = getNextAudioContent() + if (newMediaContent != null) startPlayback(true) + } + State.IDLE -> startPlayback(true) + } + } + } + + private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { release(currentMediaPlayer) currentMediaPlayer = mp - currentPlayingIndex++ - coroutineScope.launch { prepareNextFile() } + currentSequence = currentSequence?.plus(1) + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } } } return false } override fun onCompletion(mp: MediaPlayer) { - // Verify that a new media has not been set in the mean time - if (!currentMediaPlayer?.isPlaying.orFalse()) { - stop() + when { + timelineListener == null && nextMediaPlayer == null -> { + stop() + } + nextMediaPlayer == null -> { + state = State.BUFFERING + } } } @@ -181,4 +332,11 @@ class VoiceBroadcastPlayer @Inject constructor( return true } } + + enum class State { + PLAYING, + PAUSED, + BUFFERING, + IDLE + } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt new file mode 100644 index 0000000000..5b3153ea40 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt @@ -0,0 +1,41 @@ +/* + * 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.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.getRoom +import timber.log.Timber +import javax.inject.Inject + +class GetVoiceBroadcastStateUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(roomId: String, eventId: String): VoiceBroadcastState? { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + Timber.d("## GetVoiceBroadcastStateUseCase: get voice broadcast state requested for $eventId") + + val initialEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() // Fallback to initial event + val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs } + val lastVoiceBroadcastEvent = relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent + return lastVoiceBroadcastEvent?.content?.voiceBroadcastState + } +} From f05f0a85b01ade9525b9f8abbc2e4616983e17fa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 02:22:59 +0200 Subject: [PATCH 0258/1068] VoiceBroadcastRecorder - Improve recorder by sending chunk when pausing --- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 404b112574..21d12ee986 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -21,6 +21,7 @@ import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData @RequiresApi(Build.VERSION_CODES.Q) @@ -30,6 +31,7 @@ class VoiceBroadcastRecorderQ( private var maxFileSize = 0L // zero or negative for no limit private var currentSequence = 0 + private var currentRoomId: String? = null override var listener: VoiceBroadcastRecorder.Listener? = null @@ -51,11 +53,23 @@ class VoiceBroadcastRecorderQ( } override fun startRecord(roomId: String, chunkLength: Int) { + currentRoomId = roomId maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() currentSequence = 1 startRecord(roomId) } + override fun pauseRecord() { + tryOrNull { mediaRecorder?.stop() } + mediaRecorder?.reset() + notifyOutputFileCreated() + } + + override fun resumeRecord() { + currentSequence++ + currentRoomId?.let { startRecord(it) } + } + override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() From 6d6b4e52087c4c28c4beeedba00be437d6e82da6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 11:24:45 +0200 Subject: [PATCH 0259/1068] VoiceBroadcast - Ignore voice broadcast info with empty content (eg. redacted) --- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7cb66cd9e5..d5d58f822e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -55,7 +55,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( QueryStringValue.IsNotEmpty ) .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } if (onGoingVoiceBroadcastEvents.isEmpty()) { startVoiceBroadcast(room) From ed0d255495c7c6a04bb4e8d2b7f26bc28f415268 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 20 Oct 2022 14:03:42 +0200 Subject: [PATCH 0260/1068] Quick improvement on the doc. --- .../matrix/android/sdk/api/session/room/timeline/Timeline.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 1824d5dc6c..9ac33c0545 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -106,6 +106,8 @@ interface Timeline { /** * Called when new events come through the sync. + * Note that the corresponding events may not be available yet in the database. + * [onTimelineUpdated] will be called with the event content. */ fun onNewTimelineEvents(eventIds: List) = Unit From 94390697ae045e620452f6e99331c9dabf8c3e81 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:18:46 +0200 Subject: [PATCH 0261/1068] VoiceBroadcastPlayer - Filter live broadcast state listening on the referenced eventId --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 7f5e13504e..c55cb8a1d0 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -179,7 +179,11 @@ class VoiceBroadcastPlayer @Inject constructor( room.flow() .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!)) .unwrap() - .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .mapNotNull { event -> + event.asVoiceBroadcastEvent() + ?.takeIf { it.reference?.eventId == eventId } + ?.content?.voiceBroadcastState + } .onEach { state -> when (state) { VoiceBroadcastState.STARTED, From 99a2afa5ee1378050f9b2d334d72166a71a33e54 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:32:16 +0200 Subject: [PATCH 0262/1068] Add changelog --- changelog.d/7419.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7419.wip diff --git a/changelog.d/7419.wip b/changelog.d/7419.wip new file mode 100644 index 0000000000..06f69dfa7f --- /dev/null +++ b/changelog.d/7419.wip @@ -0,0 +1 @@ +[Voice Broadcast] Live listening support From bafa2f8bde785d85a82df37c4715e7a06f2b261f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:43:50 +0200 Subject: [PATCH 0263/1068] VoiceBroadcastRecorder - Send last sequence number on pause and stop --- .../app/features/voicebroadcast/VoiceBroadcastRecorder.kt | 1 + .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 2 +- .../voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt | 2 ++ .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 1 + .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 37ff920c57..c9bb0c5f54 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -23,6 +23,7 @@ import java.io.File interface VoiceBroadcastRecorder : VoiceRecorder { var listener: Listener? + var currentSequence: Int fun startRecord(roomId: String, chunkLength: Int) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 21d12ee986..a65aae6f8a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -30,8 +30,8 @@ class VoiceBroadcastRecorderQ( ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { private var maxFileSize = 0L // zero or negative for no limit - private var currentSequence = 0 private var currentRoomId: String? = null + override var currentSequence = 0 override var listener: VoiceBroadcastRecorder.Listener? = null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index a9db63c538..d882d4049e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -44,6 +44,8 @@ data class MessageVoiceBroadcastInfoContent( @Json(name = "state") val voiceBroadcastStateStr: String = "", /** The length of the voice chunks in seconds. **/ @Json(name = "chunk_length") val chunkLength: Int? = null, + /** The sequence of the last sent chunk. **/ + @Json(name = "last_chunk_sequence") val lastChunkSequence: Int? = null, ) : MessageContent { val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 835a57c102..1430dd8c86 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -59,6 +59,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( body = MessageVoiceBroadcastInfoContent( relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + lastChunkSequence = voiceBroadcastRecorder?.currentSequence, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 6eefa06979..bc6a3e7be6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -60,6 +60,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( body = MessageVoiceBroadcastInfoContent( relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value, + lastChunkSequence = voiceBroadcastRecorder?.currentSequence, ).toContent(), ) From 05eeef9dfec7b89218f4abb430435561b092b607 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 14:36:47 +0200 Subject: [PATCH 0264/1068] VoiceBroadcastListener - Handle end of live listening --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 65 ++++++------------- ...UseCase.kt => GetVoiceBroadcastUseCase.kt} | 11 ++-- 2 files changed, 25 insertions(+), 51 deletions(-) rename vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/{GetVoiceBroadcastStateUseCase.kt => GetVoiceBroadcastUseCase.kt} (80%) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index c55cb8a1d0..403675a9ee 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -23,16 +23,12 @@ import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlayb import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent -import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateUseCase +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom @@ -43,8 +39,6 @@ import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent 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.flow.flow -import org.matrix.android.sdk.flow.unwrap import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -53,7 +47,7 @@ import javax.inject.Singleton class VoiceBroadcastPlayer @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, - private val getVoiceBroadcastStateUseCase: GetVoiceBroadcastStateUseCase, + private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase, ) { private val session get() = sessionHolder.getActiveSession() @@ -87,6 +81,7 @@ class VoiceBroadcastPlayer @Inject constructor( Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value } + private var currentRoomId: String? = null fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId @@ -132,17 +127,19 @@ class VoiceBroadcastPlayer @Inject constructor( // Clear playlist playlist = emptyList() currentSequence = null + currentRoomId = null } private fun startPlayback(roomId: String, eventId: String) { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + currentRoomId = roomId // Stop listening previous voice broadcast if any if (state != State.IDLE) stop() state = State.BUFFERING - val voiceBroadcastState = getVoiceBroadcastStateUseCase.execute(roomId, eventId) + val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState if (voiceBroadcastState == VoiceBroadcastState.STOPPED) { // Get static playlist updatePlaylist(getExistingChunks(room, eventId)) @@ -172,33 +169,10 @@ class VoiceBroadcastPlayer @Inject constructor( } private fun playLiveVoiceBroadcast(room: Room, eventId: String) { - val voiceBroadcastEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() - ?: error("Cannot retrieve voice broadcast $eventId") + room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() ?: error("Cannot retrieve voice broadcast $eventId") updatePlaylist(getExistingChunks(room, eventId)) startPlayback(true) - room.flow() - .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!)) - .unwrap() - .mapNotNull { event -> - event.asVoiceBroadcastEvent() - ?.takeIf { it.reference?.eventId == eventId } - ?.content?.voiceBroadcastState - } - .onEach { state -> - when (state) { - VoiceBroadcastState.STARTED, - VoiceBroadcastState.PAUSED, - VoiceBroadcastState.RESUMED -> { - observeIncomingChunks(room, eventId) - } - VoiceBroadcastState.STOPPED -> { - currentTimeline?.dispose() - currentTimeline?.removeAllListeners() - currentTimeline = null - } - } - } - .launchIn(coroutineScope) + observeIncomingEvents(room, eventId) } private fun getExistingChunks(room: Room, eventId: String): List { @@ -207,10 +181,7 @@ class VoiceBroadcastPlayer @Inject constructor( .filter { it.isVoiceBroadcast() } } - private fun observeIncomingChunks(room: Room, eventId: String) { - // Fixme this is probably not necessary here - currentTimeline?.dispose() - currentTimeline?.removeAllListeners() + private fun observeIncomingEvents(room: Room, eventId: String) { currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline -> timelineListener = TimelineListener(eventId).also { timeline.addListener(it) } timeline.start() @@ -321,13 +292,17 @@ class VoiceBroadcastPlayer @Inject constructor( } override fun onCompletion(mp: MediaPlayer) { - when { - timelineListener == null && nextMediaPlayer == null -> { - stop() - } - nextMediaPlayer == null -> { - state = State.BUFFERING - } + if (nextMediaPlayer != null) return + val roomId = currentRoomId ?: return + val voiceBroadcastId = currentVoiceBroadcastId ?: return + val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return + val isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED + + if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) { + // We'll not receive new chunks anymore so we can stop the live listening + stop() + } else { + state = State.BUFFERING } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt similarity index 80% rename from vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt rename to vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt index 5b3153ea40..d08fa14a95 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType @@ -24,18 +24,17 @@ import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject -class GetVoiceBroadcastStateUseCase @Inject constructor( +class GetVoiceBroadcastUseCase @Inject constructor( private val session: Session, ) { - fun execute(roomId: String, eventId: String): VoiceBroadcastState? { + fun execute(roomId: String, eventId: String): VoiceBroadcastEvent? { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") - Timber.d("## GetVoiceBroadcastStateUseCase: get voice broadcast state requested for $eventId") + Timber.d("## GetVoiceBroadcastUseCase: get voice broadcast $eventId") val initialEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() // Fallback to initial event val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs } - val lastVoiceBroadcastEvent = relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent - return lastVoiceBroadcastEvent?.content?.voiceBroadcastState + return relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent } } From 0a9f2bfa0ad050ff99ea546059d868134068dfac Mon Sep 17 00:00:00 2001 From: yostyle Date: Thu, 20 Oct 2022 19:30:17 +0200 Subject: [PATCH 0265/1068] Fix some PR comments --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 403675a9ee..62252570c6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -116,8 +116,6 @@ class VoiceBroadcastPlayer @Inject constructor( voiceBroadcastStateJob = null // In case of live broadcast, stop observing new chunks - currentTimeline?.dispose() - currentTimeline?.removeAllListeners() currentTimeline = null timelineListener = null From 4c712095735fc5844ad93b5916fbe110bbf13ae2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 17:58:03 +0200 Subject: [PATCH 0266/1068] VoiceBroadcast - Add recording view --- .../src/main/res/values/strings.xml | 5 + .../ui-styles/src/main/res/values/dimens.xml | 3 + .../timeline/factory/MessageItemFactory.kt | 2 +- .../factory/VoiceBroadcastItemFactory.kt | 44 ++++-- .../item/MessageVoiceBroadcastItem.kt | 104 ------------- .../MessageVoiceBroadcastRecordingItem.kt | 137 ++++++++++++++++++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 17 ++- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 26 +++- .../usecase/StartVoiceBroadcastUseCase.kt | 8 +- .../res/drawable/ic_live_broadcast_16.xml | 21 +++ .../main/res/drawable/ic_recording_dot.xml | 9 ++ vector/src/main/res/drawable/ic_stop.xml | 9 ++ .../res/drawable/rounded_rect_shape_2.xml | 11 ++ ...em_timeline_event_voice_broadcast_stub.xml | 107 +++++++++----- 14 files changed, 337 insertions(+), 166 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt create mode 100644 vector/src/main/res/drawable/ic_live_broadcast_16.xml create mode 100644 vector/src/main/res/drawable/ic_recording_dot.xml create mode 100644 vector/src/main/res/drawable/ic_stop.xml create mode 100644 vector/src/main/res/drawable/rounded_rect_shape_2.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e6714005a1..69b4d57e28 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3078,6 +3078,11 @@ %1$s (%2$s) (%1$s) + Live + Resume voice broadcast record + Pause voice broadcast record + Stop voice broadcast record + Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 52d16eae7d..50d5aaf014 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -73,6 +73,9 @@ 12dp 22dp + + 48dp + 112dp 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 cb947a67ce..245d92f95b 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 @@ -201,7 +201,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(messageContent, params.eventsGroup, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index f2dfb020a1..1064d2bbc5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -15,46 +15,66 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider -import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem -import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_ +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class VoiceBroadcastItemFactory @Inject constructor( private val session: Session, private val avatarSizeProvider: AvatarSizeProvider, - private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, + private val colorProvider: ColorProvider, + private val drawableProvider: DrawableProvider, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { fun create( + params: TimelineItemFactoryParams, messageContent: MessageVoiceBroadcastInfoContent, - eventsGroup: TimelineEventsGroup?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastItem? { + ): MessageVoiceBroadcastRecordingItem? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId - return MessageVoiceBroadcastItem_() + return if (isRecording) { + createRecordingItem(params.event.roomId, highlight, callback, attributes) + } else { + createRecordingItem(params.event.roomId, highlight, callback, attributes) + } + } + + private fun createRecordingItem( + roomId: String, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageVoiceBroadcastRecordingItem? { + val roomSummary = session.getRoom(roomId)?.roomSummary() + return MessageVoiceBroadcastRecordingItem_() .attributes(attributes) .highlighted(highlight) - .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) - .recording(isRecording) - .audioMessagePlaybackTracker(audioMessagePlaybackTracker) + .roomItem(roomSummary?.toMatrixItem()) + .colorProvider(colorProvider) + .drawableProvider(drawableProvider) + .voiceBroadcastRecorder(voiceBroadcastRecorder) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt deleted file mode 100644 index 1927024a36..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.home.room.detail.timeline.item - -import android.annotation.SuppressLint -import android.widget.ImageButton -import android.widget.TextView -import com.airbnb.epoxy.EpoxyAttribute -import com.airbnb.epoxy.EpoxyModelClass -import im.vector.app.R -import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction -import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState - -@EpoxyModelClass -abstract class MessageVoiceBroadcastItem : AbsMessageItem() { - - @EpoxyAttribute - var callback: TimelineEventController.Callback? = null - - @EpoxyAttribute - var voiceBroadcastState: VoiceBroadcastState? = null - - @EpoxyAttribute - var recording: Boolean = false - - @EpoxyAttribute - lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker - - private val voiceBroadcastEventId - get() = attributes.informationData.eventId - - override fun isCacheable(): Boolean = false - - override fun bind(holder: Holder) { - super.bind(holder) - bindVoiceBroadcastItem(holder) - } - - @SuppressLint("SetTextI18n") // Temporary text - private fun bindVoiceBroadcastItem(holder: Holder) { - holder.currentStateText.text = "Voice Broadcast state: ${voiceBroadcastState?.value ?: "None"}" - if (recording) { - renderRecording(holder) - } else { - renderListening(holder) - } - } - - private fun renderListening(holder: Holder) { - audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener { - override fun onUpdate(state: State) { - holder.playButton.isEnabled = state !is State.Playing - holder.pauseButton.isEnabled = state is State.Playing - holder.stopButton.isEnabled = state !is State.Idle - } - }) - holder.playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastEventId)) } - holder.pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) } - holder.stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Stop) } - } - - private fun renderRecording(holder: Holder) { - with(holder) { - playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED - pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED - stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || - voiceBroadcastState == VoiceBroadcastState.RESUMED || - voiceBroadcastState == VoiceBroadcastState.PAUSED - playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - } - - override fun getViewStubId() = STUB_ID - - class Holder : AbsMessageLocationItem.Holder(STUB_ID) { - val currentStateText by bind(R.id.currentStateText) - val playButton by bind(R.id.playButton) - val pauseButton by bind(R.id.pauseButton) - val stopButton by bind(R.id.stopButton) - } - - companion object { - private val STUB_ID = R.id.messageVoiceBroadcastStub - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt new file mode 100644 index 0000000000..d271c55ebb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -0,0 +1,137 @@ +/* + * 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.home.room.detail.timeline.item + +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.extensions.tintBackground +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider +import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null + + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + + @EpoxyAttribute + var roomItem: MatrixItem? = null + + @EpoxyAttribute + var title: String? = null + + private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + + override fun isCacheable(): Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + bindVoiceBroadcastItem(holder) + } + + private fun bindVoiceBroadcastItem(holder: Holder) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderState(holder, state) + } + } + voiceBroadcastRecorder?.addListener(recorderListener) + renderHeader(holder) + } + + private fun renderHeader(holder: Holder) { + with(holder) { + roomItem?.let { + attributes.avatarRenderer.render(it, roomAvatarImageView) + titleText.text = it.displayName + } + } + } + + private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) { + with(holder) { + when (state) { + VoiceBroadcastRecorder.State.Recording -> { + stopRecordButton.isEnabled = true + + liveIndicator.isVisible = true + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError)) + + val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) + recordButton.setImageDrawable(drawable) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) + recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + VoiceBroadcastRecorder.State.Paused -> { + stopRecordButton.isEnabled = true + + liveIndicator.isVisible = true + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) + + recordButton.setImageResource(R.drawable.ic_recording_dot) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) + recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + VoiceBroadcastRecorder.State.Idle -> { + recordButton.isEnabled = false + stopRecordButton.isEnabled = false + liveIndicator.isVisible = false + } + } + } + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + voiceBroadcastRecorder?.removeListener(recorderListener) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val liveIndicator by bind(R.id.liveIndicator) + val roomAvatarImageView by bind(R.id.roomAvatarImageView) + val titleText by bind(R.id.titleText) + val recordButton by bind(R.id.recordButton) + val stopRecordButton by bind(R.id.stopRecordButton) + } + + companion object { + private val STUB_ID = R.id.messageVoiceBroadcastStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index c9bb0c5f54..8b69051823 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -22,12 +22,21 @@ import java.io.File interface VoiceBroadcastRecorder : VoiceRecorder { - var listener: Listener? - var currentSequence: Int + val currentSequence: Int + val state: State fun startRecord(roomId: String, chunkLength: Int) + fun addListener(listener: Listener) + fun removeListener(listener: Listener) - fun interface Listener { - fun onVoiceMessageCreated(file: File, @IntRange(from = 1) sequence: Int) + interface Listener { + fun onVoiceMessageCreated(file: File, @IntRange(from = 1) sequence: Int) = Unit + fun onStateUpdated(state: State) = Unit + } + + enum class State { + Recording, + Paused, + Idle, } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index a65aae6f8a..cd1a61b986 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -32,8 +32,13 @@ class VoiceBroadcastRecorderQ( private var maxFileSize = 0L // zero or negative for no limit private var currentRoomId: String? = null override var currentSequence = 0 + override var state = VoiceBroadcastRecorder.State.Idle + set(value) { + field = value + listeners.forEach { it.onStateUpdated(value) } + } - override var listener: VoiceBroadcastRecorder.Listener? = null + private val listeners = mutableListOf() override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC @@ -57,24 +62,28 @@ class VoiceBroadcastRecorderQ( maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() currentSequence = 1 startRecord(roomId) + state = VoiceBroadcastRecorder.State.Recording } override fun pauseRecord() { tryOrNull { mediaRecorder?.stop() } mediaRecorder?.reset() notifyOutputFileCreated() + state = VoiceBroadcastRecorder.State.Paused } override fun resumeRecord() { currentSequence++ currentRoomId?.let { startRecord(it) } + state = VoiceBroadcastRecorder.State.Recording } override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() - listener = null + listeners.clear() currentSequence = 0 + state = VoiceBroadcastRecorder.State.Idle } override fun release() { @@ -82,6 +91,15 @@ class VoiceBroadcastRecorderQ( super.release() } + override fun addListener(listener: VoiceBroadcastRecorder.Listener) { + listeners.add(listener) + listener.onStateUpdated(state) + } + + override fun removeListener(listener: VoiceBroadcastRecorder.Listener) { + listeners.remove(listener) + } + private fun onMaxFileSizeApproaching(roomId: String) { setNextOutputFile(roomId) } @@ -92,8 +110,8 @@ class VoiceBroadcastRecorderQ( } private fun notifyOutputFileCreated() { - outputFile?.let { - listener?.onVoiceMessageCreated(it, currentSequence) + outputFile?.let { file -> + listeners.forEach { it.onVoiceMessageCreated(file, currentSequence) } outputFile = nextOutputFile nextOutputFile = null } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index d5d58f822e..7934d18e36 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -81,9 +81,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( } private fun startRecording(room: Room, eventId: String, chunkLength: Int) { - voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file, sequence -> - sendVoiceFile(room, file, eventId, sequence) - } + voiceBroadcastRecorder?.addListener(object : VoiceBroadcastRecorder.Listener { + override fun onVoiceMessageCreated(file: File, sequence: Int) { + sendVoiceFile(room, file, eventId, sequence) + } + }) voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength) } diff --git a/vector/src/main/res/drawable/ic_live_broadcast_16.xml b/vector/src/main/res/drawable/ic_live_broadcast_16.xml new file mode 100644 index 0000000000..7d427a56d0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_live_broadcast_16.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/vector/src/main/res/drawable/ic_recording_dot.xml b/vector/src/main/res/drawable/ic_recording_dot.xml new file mode 100644 index 0000000000..f5d92f9718 --- /dev/null +++ b/vector/src/main/res/drawable/ic_recording_dot.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_stop.xml b/vector/src/main/res/drawable/ic_stop.xml new file mode 100644 index 0000000000..459a7cfce2 --- /dev/null +++ b/vector/src/main/res/drawable/ic_stop.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/rounded_rect_shape_2.xml b/vector/src/main/res/drawable/rounded_rect_shape_2.xml new file mode 100644 index 0000000000..977de2fd09 --- /dev/null +++ b/vector/src/main/res/drawable/rounded_rect_shape_2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml index e35060f72a..6773280ba5 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml @@ -5,58 +5,89 @@ android:id="@+id/messageRootLayout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@drawable/rounded_rect_shape_8" + android:backgroundTint="?vctr_content_quinary" android:padding="@dimen/layout_vertical_margin" tools:viewBindingIgnore="true"> + + + tools:src="@sample/user_round_avatars" /> + + + + + + + app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" /> - - + app:layout_constraintStart_toEndOf="@id/recordButton" + app:layout_constraintTop_toTopOf="@id/recordButton" /> From f1b4ebbc37f266702327fc9db517c8d142178387 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 18:10:32 +0200 Subject: [PATCH 0267/1068] VoiceBroadcast - Introduce listening view --- .../factory/VoiceBroadcastItemFactory.kt | 28 +++- .../MessageVoiceBroadcastListeningItem.kt | 137 ++++++++++++++++++ .../MessageVoiceBroadcastRecordingItem.kt | 2 +- ...em_timeline_event_view_stubs_container.xml | 11 +- ..._event_voice_broadcast_listening_stub.xml} | 0 ...e_event_voice_broadcast_recording_stub.xml | 93 ++++++++++++ 6 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt rename vector/src/main/res/layout/{item_timeline_event_voice_broadcast_stub.xml => item_timeline_event_voice_broadcast_listening_stub.xml} (100%) create mode 100644 vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 1064d2bbc5..13a38ac4be 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -15,12 +15,16 @@ */ package im.vector.app.features.home.room.detail.timeline.factory +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_ import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder @@ -46,7 +50,7 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastRecordingItem? { + ): VectorEpoxyModel? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null @@ -57,7 +61,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createRecordingItem(params.event.roomId, highlight, callback, attributes) + createListeningItem(params.event.roomId, highlight, callback, attributes) } } @@ -66,7 +70,7 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageVoiceBroadcastRecordingItem? { + ): MessageVoiceBroadcastRecordingItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() .attributes(attributes) @@ -78,4 +82,22 @@ class VoiceBroadcastItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } + + private fun createListeningItem( + roomId: String, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageVoiceBroadcastListeningItem { + val roomSummary = session.getRoom(roomId)?.roomSummary() + return MessageVoiceBroadcastListeningItem_() + .attributes(attributes) + .highlighted(highlight) + .roomItem(roomSummary?.toMatrixItem()) + .colorProvider(colorProvider) + .drawableProvider(drawableProvider) + .voiceBroadcastRecorder(voiceBroadcastRecorder) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt new file mode 100644 index 0000000000..e5d0fd6c30 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -0,0 +1,137 @@ +/* + * 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.home.room.detail.timeline.item + +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.extensions.tintBackground +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider +import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null + + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + + @EpoxyAttribute + var roomItem: MatrixItem? = null + + @EpoxyAttribute + var title: String? = null + + private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + + override fun isCacheable(): Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + bindVoiceBroadcastItem(holder) + } + + private fun bindVoiceBroadcastItem(holder: Holder) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderState(holder, state) + } + } + voiceBroadcastRecorder?.addListener(recorderListener) + renderHeader(holder) + } + + private fun renderHeader(holder: Holder) { + with(holder) { + roomItem?.let { + attributes.avatarRenderer.render(it, roomAvatarImageView) + titleText.text = it.displayName + } + } + } + + private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) { + with(holder) { + when (state) { + VoiceBroadcastRecorder.State.Recording -> { + stopRecordButton.isEnabled = true + + liveIndicator.isVisible = true + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError)) + + val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) + recordButton.setImageDrawable(drawable) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) + recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + VoiceBroadcastRecorder.State.Paused -> { + stopRecordButton.isEnabled = true + + liveIndicator.isVisible = true + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) + + recordButton.setImageResource(R.drawable.ic_recording_dot) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) + recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + VoiceBroadcastRecorder.State.Idle -> { + recordButton.isEnabled = false + stopRecordButton.isEnabled = false + liveIndicator.isVisible = false + } + } + } + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + voiceBroadcastRecorder?.removeListener(recorderListener) + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageItem.Holder(STUB_ID) { + val liveIndicator by bind(R.id.liveIndicator) + val roomAvatarImageView by bind(R.id.roomAvatarImageView) + val titleText by bind(R.id.titleText) + val recordButton by bind(R.id.recordButton) + val stopRecordButton by bind(R.id.stopRecordButton) + } + + companion object { + private val STUB_ID = R.id.messageVoiceBroadcastListeningStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index d271c55ebb..30475c1659 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -132,6 +132,6 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem + + + + + + + + + + + + + + + + + + + From f711a0ea740f14f4d63eff35d67355e5224bdf57 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 19:07:34 +0200 Subject: [PATCH 0268/1068] VoiceBroadcast - Listening view --- .../src/main/res/values/strings.xml | 3 + .../factory/VoiceBroadcastItemFactory.kt | 11 ++- .../MessageVoiceBroadcastListeningItem.kt | 97 +++++++++++-------- .../voicebroadcast/VoiceBroadcastPlayer.kt | 17 +++- ...e_event_voice_broadcast_listening_stub.xml | 32 +++--- 5 files changed, 98 insertions(+), 62 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 69b4d57e28..ea9b4b5999 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3082,6 +3082,9 @@ Resume voice broadcast record Pause voice broadcast record Stop voice broadcast record + Play or resume voice broadcast + Pause voice broadcast + Buffering Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 13a38ac4be..af14d532c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -27,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadca import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_ +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -42,6 +43,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private val colorProvider: ColorProvider, private val drawableProvider: DrawableProvider, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, + private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { fun create( @@ -53,7 +55,8 @@ class VoiceBroadcastItemFactory @Inject constructor( ): VectorEpoxyModel? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val eventsGroup = params.eventsGroup ?: return null + val voiceBroadcastEventsGroup = VoiceBroadcastEventsGroup(eventsGroup) val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null @@ -61,7 +64,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, highlight, callback, attributes) + createListeningItem(params.event.roomId, eventsGroup.groupId, highlight, callback, attributes) } } @@ -85,6 +88,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createListeningItem( roomId: String, + voiceBroadcastId: String, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -96,7 +100,8 @@ class VoiceBroadcastItemFactory @Inject constructor( .roomItem(roomSummary?.toMatrixItem()) .colorProvider(colorProvider) .drawableProvider(drawableProvider) - .voiceBroadcastRecorder(voiceBroadcastRecorder) + .voiceBroadcastPlayer(voiceBroadcastPlayer) + .voiceBroadcastId(voiceBroadcastId) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index e5d0fd6c30..3a090b0eb6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.view.View import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView @@ -23,12 +24,11 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider -import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction +import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass @@ -38,7 +38,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem + renderState(holder, state) } - voiceBroadcastRecorder?.addListener(recorderListener) + voiceBroadcastPlayer?.addListener(playerListener) renderHeader(holder) } @@ -80,45 +81,59 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { - stopRecordButton.isEnabled = true - - liveIndicator.isVisible = true - liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorOnError)) - - val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) - val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) - recordButton.setImageDrawable(drawable) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) - recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + VoiceBroadcastPlayer.State.PLAYING -> { + playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) + playPauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } - VoiceBroadcastRecorder.State.Paused -> { - stopRecordButton.isEnabled = true - - liveIndicator.isVisible = true - liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) - - recordButton.setImageResource(R.drawable.ic_recording_dot) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.setOnClickListener { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Idle -> { - recordButton.isEnabled = false - stopRecordButton.isEnabled = false - liveIndicator.isVisible = false + VoiceBroadcastPlayer.State.IDLE, + VoiceBroadcastPlayer.State.PAUSED -> { + playPauseButton.setImageResource(R.drawable.ic_play_pause_play) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) + playPauseButton.setOnClickListener { + attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + } } + VoiceBroadcastPlayer.State.BUFFERING -> Unit } } } + private fun renderInactiveMedia(holder: Holder) { + with(holder) { + liveIndicator.isVisible = false + bufferingView.isVisible = false + playPauseButton.isVisible = true + playPauseButton.setImageResource(R.drawable.ic_play_pause_play) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) + playPauseButton.setOnClickListener { + attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + } + } + } + + private fun isCurrentMediaActive() = voiceBroadcastPlayer?.currentVoiceBroadcastId == voiceBroadcastId + override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastRecorder?.removeListener(recorderListener) + voiceBroadcastPlayer?.removeListener(playerListener) } override fun getViewStubId() = STUB_ID @@ -127,8 +142,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem(R.id.liveIndicator) val roomAvatarImageView by bind(R.id.roomAvatarImageView) val titleText by bind(R.id.titleText) - val recordButton by bind(R.id.recordButton) - val stopRecordButton by bind(R.id.stopRecordButton) + val playPauseButton by bind(R.id.playPauseButton) + val bufferingView by bind(R.id.bufferingView) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 62252570c6..9c118f957d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -73,15 +73,17 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentSequence: Int? = null private var playlist = emptyList() - private val currentVoiceBroadcastId + val currentVoiceBroadcastId get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId private var state: State = State.IDLE set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value + listeners.forEach { it.onStateChanged(value) } } private var currentRoomId: String? = null + private var listeners = mutableListOf() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId @@ -128,6 +130,15 @@ class VoiceBroadcastPlayer @Inject constructor( currentRoomId = null } + fun addListener(listener: Listener) { + listeners.add(listener) + listener.onStateChanged(state) + } + + fun removeListener(listener: Listener) { + listeners.remove(listener) + } + private fun startPlayback(roomId: String, eventId: String) { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") currentRoomId = roomId @@ -316,4 +327,8 @@ class VoiceBroadcastPlayer @Inject constructor( BUFFERING, IDLE } + + fun interface Listener { + fun onStateChanged(state: State) + } } diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 6773280ba5..506ca4ff47 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -65,29 +65,27 @@ app:constraint_referenced_ids="roomAvatarImageView,titleText" /> + + - - From cebc096ac70dad17ebef02f5cc3882ae2b5e93d2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:06:27 +0200 Subject: [PATCH 0269/1068] VoiceBroadcast - Update live indicator icon --- .../factory/VoiceBroadcastItemFactory.kt | 4 ++- .../MessageVoiceBroadcastListeningItem.kt | 28 +++++++++++++++++-- .../MessageVoiceBroadcastRecordingItem.kt | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index af14d532c8..5a70f66d75 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -64,7 +64,7 @@ class VoiceBroadcastItemFactory @Inject constructor( return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, eventsGroup.groupId, highlight, callback, attributes) + createListeningItem(params.event.roomId, eventsGroup.groupId, mostRecentMessageContent.voiceBroadcastState, highlight, callback, attributes) } } @@ -89,6 +89,7 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createListeningItem( roomId: String, voiceBroadcastId: String, + voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -102,6 +103,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .drawableProvider(drawableProvider) .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) + .voiceBroadcastState(voiceBroadcastState) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 3a090b0eb6..bf0f202f0c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -24,11 +24,13 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass @@ -43,6 +45,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) + liveIndicator.isVisible = true + } + VoiceBroadcastState.PAUSED -> { + liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) + liveIndicator.isVisible = true + } + VoiceBroadcastState.STOPPED, null -> { + liveIndicator.isVisible = false + } + } + } + } + private fun renderState(holder: Holder, state: VoiceBroadcastPlayer.State) { if (isCurrentMediaActive()) { renderActiveMedia(holder, state) @@ -94,8 +119,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { @@ -118,7 +141,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem Date: Thu, 20 Oct 2022 21:11:00 +0200 Subject: [PATCH 0270/1068] Fix null voiceBroadcastId when the playlist is empty --- .../features/voicebroadcast/VoiceBroadcastPlayer.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 9c118f957d..28ed7a58b8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -73,8 +72,7 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentSequence: Int? = null private var playlist = emptyList() - val currentVoiceBroadcastId - get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId + var currentVoiceBroadcastId: String? = null private var state: State = State.IDLE set(value) { @@ -128,6 +126,7 @@ class VoiceBroadcastPlayer @Inject constructor( playlist = emptyList() currentSequence = null currentRoomId = null + currentVoiceBroadcastId = null } fun addListener(listener: Listener) { @@ -141,11 +140,12 @@ class VoiceBroadcastPlayer @Inject constructor( private fun startPlayback(roomId: String, eventId: String) { val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") - currentRoomId = roomId - // Stop listening previous voice broadcast if any if (state != State.IDLE) stop() + currentRoomId = roomId + currentVoiceBroadcastId = eventId + state = State.BUFFERING val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState From 6ff7a7f3aeb43bd8819d3d53d3a20125eada8125 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:20:17 +0200 Subject: [PATCH 0271/1068] Update buffering view --- ...item_timeline_event_voice_broadcast_listening_stub.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 506ca4ff47..6a4fcef77a 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -78,14 +78,18 @@ app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" app:tint="?vctr_content_secondary" /> - + From 72a1acec8959a0db909e529f0e7ac585d537cf2f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 21:26:39 +0200 Subject: [PATCH 0272/1068] Fix voice broadcast state update on wrong thread --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 28ed7a58b8..bb5fe5c37d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast import android.media.AudioAttributes import android.media.MediaPlayer +import androidx.annotation.MainThread import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.voice.VoiceFailure @@ -29,6 +30,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room @@ -75,6 +77,7 @@ class VoiceBroadcastPlayer @Inject constructor( var currentVoiceBroadcastId: String? = null private var state: State = State.IDLE + @MainThread set(value) { Timber.w("## VoiceBroadcastPlayer state: $field -> $value") field = value @@ -168,7 +171,7 @@ class VoiceBroadcastPlayer @Inject constructor( currentMediaPlayer?.start() currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } currentSequence = sequence - state = State.PLAYING + withContext(Dispatchers.Main) { state = State.PLAYING } nextMediaPlayer = prepareNextMediaPlayer() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") From 930c85672866c5d83c47795656d40c8b740639e6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:38:02 +0200 Subject: [PATCH 0273/1068] Add additional information in listening tile --- .../factory/VoiceBroadcastItemFactory.kt | 14 ++++- .../MessageVoiceBroadcastListeningItem.kt | 6 +- ...dcast_16.xml => ic_voice_broadcast_16.xml} | 0 ...e_event_voice_broadcast_listening_stub.xml | 60 +++++++++++++++++-- ...e_event_voice_broadcast_recording_stub.xml | 2 +- 5 files changed, 75 insertions(+), 7 deletions(-) rename vector/src/main/res/drawable/{ic_live_broadcast_16.xml => ic_voice_broadcast_16.xml} (100%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 5a70f66d75..98f45cd5db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -34,6 +34,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -61,10 +62,19 @@ class VoiceBroadcastItemFactory @Inject constructor( val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId + val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey return if (isRecording) { createRecordingItem(params.event.roomId, highlight, callback, attributes) } else { - createListeningItem(params.event.roomId, eventsGroup.groupId, mostRecentMessageContent.voiceBroadcastState, highlight, callback, attributes) + createListeningItem( + params.event.roomId, + eventsGroup.groupId, + mostRecentMessageContent.voiceBroadcastState, + recorderName, + highlight, + callback, + attributes + ) } } @@ -90,6 +100,7 @@ class VoiceBroadcastItemFactory @Inject constructor( roomId: String, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, + broadcasterName: String?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, @@ -104,6 +115,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) .voiceBroadcastState(voiceBroadcastState) + .broadcasterName(broadcasterName) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index bf0f202f0c..130d44202f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -48,6 +48,9 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem(R.id.titleText) val playPauseButton by bind(R.id.playPauseButton) val bufferingView by bind(R.id.bufferingView) + val broadcasterNameText by bind(R.id.broadcasterNameText) } companion object { diff --git a/vector/src/main/res/drawable/ic_live_broadcast_16.xml b/vector/src/main/res/drawable/ic_voice_broadcast_16.xml similarity index 100% rename from vector/src/main/res/drawable/ic_live_broadcast_16.xml rename to vector/src/main/res/drawable/ic_voice_broadcast_16.xml diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 6a4fcef77a..73acde2942 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -24,7 +24,7 @@ android:singleLine="true" android:text="@string/voice_broadcast_live" android:textColor="?colorOnError" - app:drawableStartCompat="@drawable/ic_live_broadcast_16" + app:drawableStartCompat="@drawable/ic_voice_broadcast_16" app:drawableTint="?colorOnError" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -36,7 +36,7 @@ android:contentDescription="@string/avatar" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:src="@sample/user_round_avatars" /> + tools:src="@sample/room_round_avatars" /> + tools:src="@sample/rooms.json/data/name" /> + + + + + + + + + + + + + + + app:constraint_referenced_ids="roomAvatarImageView,titleText,broadcasterViewGroup,voiceBroadcastViewGroup" /> From 8869d82dd0f7a3e2aa232456f7cf324daaf04d16 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:39:27 +0200 Subject: [PATCH 0274/1068] Add changelog --- changelog.d/7421.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7421.wip diff --git a/changelog.d/7421.wip b/changelog.d/7421.wip new file mode 100644 index 0000000000..4a399eee04 --- /dev/null +++ b/changelog.d/7421.wip @@ -0,0 +1 @@ +[Voice Broadcast] Improve rendering in the timeline From 9a96de4f06f60ed47261f9cb34b743db4e169918 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:45:22 +0200 Subject: [PATCH 0275/1068] Set id to VoiceBroadcast items --- .../timeline/factory/VoiceBroadcastItemFactory.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 98f45cd5db..5dc601a91a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -62,9 +62,15 @@ class VoiceBroadcastItemFactory @Inject constructor( val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() val mostRecentMessageContent = mostRecentEvent?.content ?: return null val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId - val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey + val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey return if (isRecording) { - createRecordingItem(params.event.roomId, highlight, callback, attributes) + createRecordingItem( + params.event.roomId, + eventsGroup.groupId, + highlight, + callback, + attributes + ) } else { createListeningItem( params.event.roomId, @@ -80,12 +86,14 @@ class VoiceBroadcastItemFactory @Inject constructor( private fun createRecordingItem( roomId: String, + voiceBroadcastId: String, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastRecordingItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() + .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) .highlighted(highlight) .roomItem(roomSummary?.toMatrixItem()) @@ -107,6 +115,7 @@ class VoiceBroadcastItemFactory @Inject constructor( ): MessageVoiceBroadcastListeningItem { val roomSummary = session.getRoom(roomId)?.roomSummary() return MessageVoiceBroadcastListeningItem_() + .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) .highlighted(highlight) .roomItem(roomSummary?.toMatrixItem()) From f2cc08263fa59f3ca58b3ac41a7508b04aa65117 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:48:09 +0200 Subject: [PATCH 0276/1068] Call onClick instead of setOnClickListener --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 7 ++++--- .../timeline/item/MessageVoiceBroadcastRecordingItem.kt | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 130d44202f..5b58dda4e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -24,6 +24,7 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider @@ -127,13 +128,13 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) - playPauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } + playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } VoiceBroadcastPlayer.State.IDLE, VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) - playPauseButton.setOnClickListener { + playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } } @@ -148,7 +149,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem { stopRecordButton.isEnabled = true @@ -104,8 +105,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { recordButton.isEnabled = false From 4a76998c98bec82af2cbe8e436f7edbd07f7bfaa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 22:50:09 +0200 Subject: [PATCH 0277/1068] Use CopyOnWriteArrayList --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 3 ++- .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index bb5fe5c37d..2c892c8306 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -41,6 +41,7 @@ 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 timber.log.Timber +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject import javax.inject.Singleton @@ -84,7 +85,7 @@ class VoiceBroadcastPlayer @Inject constructor( listeners.forEach { it.onStateChanged(value) } } private var currentRoomId: String? = null - private var listeners = mutableListOf() + private var listeners = CopyOnWriteArrayList() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index cd1a61b986..5285dc5e3b 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -23,6 +23,7 @@ import androidx.annotation.RequiresApi import im.vector.app.features.voice.AbstractVoiceRecorderQ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.util.concurrent.CopyOnWriteArrayList @RequiresApi(Build.VERSION_CODES.Q) class VoiceBroadcastRecorderQ( @@ -38,7 +39,7 @@ class VoiceBroadcastRecorderQ( listeners.forEach { it.onStateUpdated(value) } } - private val listeners = mutableListOf() + private val listeners = CopyOnWriteArrayList() override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC From 34cafa373f5f29a65d3d4e3318bfe0273396c149 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 23:43:33 +0200 Subject: [PATCH 0278/1068] Add missing content description --- .../item_timeline_event_voice_broadcast_listening_stub.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 73acde2942..248c04a2f6 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -71,6 +71,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginEnd="5dp" + android:contentDescription="@null" android:src="@drawable/ic_microphone" app:tint="?vctr_content_secondary" /> @@ -97,6 +98,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginEnd="5dp" + android:contentDescription="@null" android:src="@drawable/ic_voice_broadcast_16" app:tint="?vctr_content_secondary" /> From 926f4d920180e40905f253d00df93aef714793ae Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 23:58:17 +0200 Subject: [PATCH 0279/1068] Fix play/pause button disabled --- .../detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 41ef1fac9c..c417053b2a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -86,6 +86,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { stopRecordButton.isEnabled = true + recordButton.isEnabled = true liveIndicator.isVisible = true liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError)) @@ -99,6 +100,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem { stopRecordButton.isEnabled = true + recordButton.isEnabled = true liveIndicator.isVisible = true liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary)) From 1086ed367eae93be7443eb9b42b82635244579ed Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 20 Oct 2022 19:42:06 -0400 Subject: [PATCH 0280/1068] Fixes thread notifications instantly disappearing --- .../room/summary/RoomSummaryUpdater.kt | 10 ++-- .../home/room/detail/TimelineViewModel.kt | 51 +++++++++++++------ 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index c740e07257..7c83a4afa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -114,14 +114,12 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 roomSummaryEntity.threadHighlightCount = unreadThreadNotifications - ?.map { it.value.highlightCount.takeIf { count -> (count ?: 0) > 0 } } - ?.size - ?: 0 - roomSummaryEntity.threadNotificationCount = unreadThreadNotifications - ?.map { it.value.notificationCount.takeIf { count -> (count ?: 0) > 0 } } - ?.size + ?.count { (it.value.highlightCount ?: 0) > 0 } ?: 0 + roomSummaryEntity.threadNotificationCount = unreadThreadNotifications + ?.count { (it.value.notificationCount ?: 0) > 0 } + ?: 0 if (membership != null) { roomSummaryEntity.membership = membership 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 511fd597fe..c30d7a648d 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 @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -407,22 +408,40 @@ class TimelineViewModel @AssistedInject constructor( * Observe local unread threads. */ private fun observeLocalThreadNotifications() { - if (room == null) return - room.flow() - .liveLocalUnreadThreadList() - .execute { - val threadList = it.invoke() - val isUserMentioned = threadList?.firstOrNull { threadRootEvent -> - threadRootEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE - }?.let { true } ?: false - val numberOfLocalUnreadThreads = threadList?.size ?: 0 - copy( - threadNotificationBadgeState = ThreadNotificationBadgeState( - numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, - isUserMentioned = isUserMentioned - ) - ) - } + val threadNotificationsSupported = session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications + if (threadNotificationsSupported) { + room?.getRoomSummaryLive() + ?.asFlow() + ?.onEach { + it.getOrNull()?.let { + setState { + copy( + threadNotificationBadgeState = ThreadNotificationBadgeState( + numberOfLocalUnreadThreads = it.threadNotificationCount + it.threadHighlightCount, + isUserMentioned = it.threadHighlightCount > 0, + ) + ) + } + } + } + ?.launchIn(viewModelScope) + } else { + room?.flow() + ?.liveLocalUnreadThreadList() + ?.execute { + val threadList = it.invoke() + val isUserMentioned = threadList?.firstOrNull { threadRootEvent -> + threadRootEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE + }?.let { true } ?: false + val numberOfLocalUnreadThreads = threadList?.size ?: 0 + copy( + threadNotificationBadgeState = ThreadNotificationBadgeState( + numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, + isUserMentioned = isUserMentioned + ) + ) + } + } } override fun handle(action: RoomDetailAction) { From f76490130cab871b7dade83b7a1a03aaeb0ae48c Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Thu, 20 Oct 2022 19:49:37 -0400 Subject: [PATCH 0281/1068] Adds changelog file --- changelog.d/7424.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7424.misc diff --git a/changelog.d/7424.misc b/changelog.d/7424.misc new file mode 100644 index 0000000000..6c6673eaf0 --- /dev/null +++ b/changelog.d/7424.misc @@ -0,0 +1 @@ +Gets thread notifications from sync response From 8a4d918b250325949c2bb6c0a06bc99c586a7bf9 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Fri, 21 Oct 2022 09:59:40 +0100 Subject: [PATCH 0282/1068] Update triage-labelled.yml --- .github/workflows/triage-labelled.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 18ce26171c..34e36a55da 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -52,8 +52,8 @@ jobs: (contains(github.event.issue.labels.*.name, 'S-Critical') && (contains(github.event.issue.labels.*.name, 'O-Frequent') || contains(github.event.issue.labels.*.name, 'O-Occasional')) || - contains(github.event.issue.labels.*.name, 'S-Major') && - contains(github.event.issue.labels.*.name, 'O-Frequent') || + (contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) || contains(github.event.issue.labels.*.name, 'A11y')) steps: - uses: octokit/graphql-action@v2.x From 113d0ff5400860337deb5786c105cfeba655363e Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:06:20 +0200 Subject: [PATCH 0283/1068] =?UTF-8?q?thread=20read=20receipts=20and=20unre?= =?UTF-8?q?ad=20notifications=20support=20is=20added=20to=20hom=E2=80=A6?= =?UTF-8?q?=20(#7386)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homeserver/HomeServerCapabilities.kt | 5 +++ .../auth/version/HomeServerVersion.kt | 1 + .../sdk/internal/auth/version/Versions.kt | 11 ++++++ .../database/RealmSessionStoreMigration.kt | 4 ++- .../mapper/HomeServerCapabilitiesMapper.kt | 1 + .../database/migration/MigrateSessionTo040.kt | 34 +++++++++++++++++++ .../model/HomeServerCapabilitiesEntity.kt | 1 + .../GetHomeServerCapabilitiesTask.kt | 3 ++ 8 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt 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 8c14ca892a..773e870ffd 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 @@ -65,6 +65,11 @@ data class HomeServerCapabilities( * True if the home server supports login via qr code, false otherwise. */ val canLoginWithQrCode: Boolean = false, + + /** + * True if the home server supports threaded read receipts and unread notifications. + */ + val canUseThreadReadReceiptsAndNotifications: Boolean = false, ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index 75639c6a21..d443d6e3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -60,5 +60,6 @@ internal data class HomeServerVersion( val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) + val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index 5e133fab9c..1245d8df4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -54,6 +54,8 @@ private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind" private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440" private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable" private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" +private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" +private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" /** * Return true if the SDK supports this homeserver version. @@ -79,6 +81,15 @@ internal fun Versions.doesServerSupportThreads(): Boolean { return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false } +/** + * Indicate if the homeserver support MSC3771 and MSC3773 for threaded read receipts and unread notifications. + */ +internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean { + val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false + val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false + return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773) +} + internal fun Versions.doesServerSupportQrCodeLogin(): Boolean { return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false } 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 9a2c32f97c..def0f6de7a 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 @@ -56,6 +56,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -64,7 +65,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 39L, + schemaVersion = 40L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -113,5 +114,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 37) MigrateSessionTo037(realm).perform() if (oldVersion < 38) MigrateSessionTo038(realm).perform() if (oldVersion < 39) MigrateSessionTo039(realm).perform() + if (oldVersion < 40) MigrateSessionTo040(realm).perform() } } 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 63fa101c45..3528ca0051 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 @@ -45,6 +45,7 @@ internal object HomeServerCapabilitiesMapper { canUseThreading = entity.canUseThreading, canControlLogoutDevices = entity.canControlLogoutDevices, canLoginWithQrCode = entity.canLoginWithQrCode, + canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt new file mode 100644 index 0000000000..b3e02342dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt @@ -0,0 +1,34 @@ +/* + * 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 org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo040(realm: DynamicRealm) : RealmMigrator(realm, 40) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, false) + } + ?.forceRefreshOfHomeServerCapabilities() + } +} 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 cfa02b2c74..89f1e50b30 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 @@ -32,6 +32,7 @@ internal open class HomeServerCapabilitiesEntity( var canUseThreading: Boolean = false, var canControlLogoutDevices: Boolean = false, var canLoginWithQrCode: Boolean = false, + var canUseThreadReadReceiptsAndNotifications: Boolean = false, ) : RealmObject() { companion object 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 2c3cb440b6..a5953d870c 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 @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin +import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity @@ -144,6 +145,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ getVersionResult.doesServerSupportThreads() + homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications = + getVersionResult.doesServerSupportThreadUnreadNotifications() homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin() } From 31811bb7e0eff83b1fcabc1cd27298ebb121bc5e Mon Sep 17 00:00:00 2001 From: jonnyandrew Date: Fri, 21 Oct 2022 17:36:31 +0100 Subject: [PATCH 0284/1068] Fix crash by disabling Flipper on API 22 and below (#7428) * Disable Flipper on API 21 and below - only affects debug builds. Due to a bug: https://github.com/facebook/flipper/issues/3572 * Add jonnyandrew to PR sign-off allowlist Co-authored-by: Benoit Marty --- changelog.d/7428.bugfix | 1 + tools/danger/dangerfile.js | 1 + .../vector/app/flipper/VectorFlipperProxy.kt | 56 ++++++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 changelog.d/7428.bugfix diff --git a/changelog.d/7428.bugfix b/changelog.d/7428.bugfix new file mode 100644 index 0000000000..8f014af31b --- /dev/null +++ b/changelog.d/7428.bugfix @@ -0,0 +1 @@ +Fix crash by disabling Flipper on Android API 22 and below - only affects debug version of the application. diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js index 1a36474470..2fc55bad0c 100644 --- a/tools/danger/dangerfile.js +++ b/tools/danger/dangerfile.js @@ -81,6 +81,7 @@ const allowList = [ "Florian14", "ganfra", "jmartinesp", + "jonnyandrew", "langleyd", "MadLittleMods", "manuroe", diff --git a/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt b/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt index 2e4336c942..cbf9e4f11f 100644 --- a/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt +++ b/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt @@ -17,6 +17,7 @@ package im.vector.app.flipper import android.content.Context +import android.os.Build import com.facebook.flipper.android.AndroidFlipperClient import com.facebook.flipper.android.utils.FlipperUtils import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin @@ -31,6 +32,7 @@ import com.kgurgul.flipper.RealmDatabaseDriver import com.kgurgul.flipper.RealmDatabaseProvider import im.vector.app.core.debug.FlipperProxy import io.realm.RealmConfiguration +import okhttp3.Interceptor import org.matrix.android.sdk.api.Matrix import javax.inject.Inject import javax.inject.Singleton @@ -41,29 +43,43 @@ class VectorFlipperProxy @Inject constructor( ) : FlipperProxy { private val networkFlipperPlugin = NetworkFlipperPlugin() + private val isEnabled: Boolean + get() { + // https://github.com/facebook/flipper/issues/3572 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + return false + } + + return FlipperUtils.shouldEnableFlipper(context) + } + override fun init(matrix: Matrix) { + if (!isEnabled) return + SoLoader.init(context, false) - if (FlipperUtils.shouldEnableFlipper(context)) { - val client = AndroidFlipperClient.getInstance(context) - client.addPlugin(CrashReporterPlugin.getInstance()) - client.addPlugin(SharedPreferencesFlipperPlugin(context)) - client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())) - client.addPlugin(networkFlipperPlugin) - client.addPlugin( - DatabasesFlipperPlugin( - RealmDatabaseDriver( - context = context, - realmDatabaseProvider = object : RealmDatabaseProvider { - override fun getRealmConfigurations(): List { - return matrix.debugService().getAllRealmConfigurations() - } - }) - ) - ) - client.start() - } + val client = AndroidFlipperClient.getInstance(context) + client.addPlugin(CrashReporterPlugin.getInstance()) + client.addPlugin(SharedPreferencesFlipperPlugin(context)) + client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())) + client.addPlugin(networkFlipperPlugin) + client.addPlugin( + DatabasesFlipperPlugin( + RealmDatabaseDriver( + context = context, + realmDatabaseProvider = object : RealmDatabaseProvider { + override fun getRealmConfigurations(): List { + return matrix.debugService().getAllRealmConfigurations() + } + }) + ) + ) + client.start() } - override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin) + override fun networkInterceptor(): Interceptor? { + if (!isEnabled) return null + + return FlipperOkhttpInterceptor(networkFlipperPlugin) + } } From bec71438247f6fdf67b429499d92731ed2264259 Mon Sep 17 00:00:00 2001 From: Jonny Andrew Date: Thu, 20 Oct 2022 16:13:17 +0100 Subject: [PATCH 0285/1068] Add new attachments selection dialog --- .../src/main/res/values/strings.xml | 10 ++ .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../core/ui/views/BottomSheetActionButton.kt | 5 + .../features/attachments/AttachmentType.kt | 37 ++++++++ .../AttachmentTypeSelectorBottomSheet.kt | 79 ++++++++++++++++ ...chmentTypeSelectorSharedActionViewModel.kt | 30 ++++++ .../attachments/AttachmentTypeSelectorView.kt | 70 +++++++------- .../AttachmentTypeSelectorViewModel.kt | 59 ++++++++++++ .../features/attachments/AttachmentsHelper.kt | 2 +- .../composer/MessageComposerFragment.kt | 64 ++++++++----- .../bottom_sheet_attachment_type_selector.xml | 92 +++++++++++++++++++ .../AttachmentTypeSelectorViewModelTest.kt | 91 ++++++++++++++++++ .../app/test/fakes/FakeVectorFeatures.kt | 8 ++ 13 files changed, 492 insertions(+), 61 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml create mode 100644 vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index ea9b4b5999..9ce87199b0 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3205,6 +3205,16 @@ Share location Start a voice broadcast + Photo library + Stickers + Attachments + Voice broadcast + Polls + Location + Camera + Contact + Text formatting + Show less "%1$d more" diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 97590028d8..2242abb7aa 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -22,6 +22,7 @@ import dagger.hilt.InstallIn import dagger.multibindings.IntoMap import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel +import im.vector.app.features.attachments.AttachmentTypeSelectorViewModel import im.vector.app.features.auth.ReAuthViewModel import im.vector.app.features.call.VectorCallViewModel import im.vector.app.features.call.conference.JitsiCallViewModel @@ -677,4 +678,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(VectorSettingsLabsViewModel::class) fun vectorSettingsLabsViewModelFactory(factory: VectorSettingsLabsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(AttachmentTypeSelectorViewModel::class) + fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt index a3e8b3780c..d16bf67a34 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt @@ -38,6 +38,11 @@ class BottomSheetActionButton @JvmOverloads constructor( ) : FrameLayout(context, attrs, defStyleAttr) { val views: ViewBottomSheetActionButtonBinding + override fun setOnClickListener(l: OnClickListener?) { + super.setOnClickListener(l) + views.bottomSheetActionClickableZone.setOnClickListener(l) + } + var title: String? = null set(value) { field = value diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt new file mode 100644 index 0000000000..f4b97b9f9c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt @@ -0,0 +1,37 @@ +/* + * 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.attachments + +import im.vector.app.core.utils.PERMISSIONS_EMPTY +import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING +import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT +import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_BROADCAST + +/** + * The all possible types to pick with their required permissions. + */ +enum class AttachmentType(val permissions: List) { + CAMERA(PERMISSIONS_FOR_TAKING_PHOTO), + GALLERY(PERMISSIONS_EMPTY), + FILE(PERMISSIONS_EMPTY), + STICKER(PERMISSIONS_EMPTY), + CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), + POLL(PERMISSIONS_EMPTY), + LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING), + VOICE_BROADCAST(PERMISSIONS_FOR_VOICE_BROADCAST), +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt new file mode 100644 index 0000000000..88cad2ab0b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt @@ -0,0 +1,79 @@ +/* + * 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.attachments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.viewModels +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetAttachmentTypeSelectorBinding +import im.vector.app.features.home.room.detail.TimelineViewModel + +@AndroidEntryPoint +class AttachmentTypeSelectorBottomSheet : VectorBaseBottomSheetDialogFragment() { + + private val viewModel: AttachmentTypeSelectorViewModel by fragmentViewModel() + private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() + private val sharedActionViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels( + ownerProducer = { requireParentFragment() } + ) + + override val showExpanded = true + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetAttachmentTypeSelectorBinding { + return BottomSheetAttachmentTypeSelectorBinding.inflate(inflater, container, false) + } + + override fun invalidate() = withState(viewModel, timelineViewModel) { viewState, timelineState -> + super.invalidate() + views.location.visibility = if (viewState.isLocationVisible) View.VISIBLE else View.GONE + views.voiceBroadcast.visibility = if (viewState.isVoiceBroadcastVisible) View.VISIBLE else View.GONE + views.poll.visibility = if (!timelineState.isThreadTimeline()) View.VISIBLE else View.GONE + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + views.gallery.debouncedClicks { onAttachmentSelected(AttachmentType.GALLERY) } + views.stickers.debouncedClicks { onAttachmentSelected(AttachmentType.STICKER) } + views.file.debouncedClicks { onAttachmentSelected(AttachmentType.FILE) } + views.voiceBroadcast.debouncedClicks { onAttachmentSelected(AttachmentType.VOICE_BROADCAST) } + views.poll.debouncedClicks { onAttachmentSelected(AttachmentType.POLL) } + views.location.debouncedClicks { onAttachmentSelected(AttachmentType.LOCATION) } + views.camera.debouncedClicks { onAttachmentSelected(AttachmentType.CAMERA) } + views.contact.debouncedClicks { onAttachmentSelected(AttachmentType.CONTACT) } + } + + private fun onAttachmentSelected(attachmentType: AttachmentType) { + val action = AttachmentTypeSelectorSharedAction.SelectAttachmentTypeAction(attachmentType) + sharedActionViewModel.post(action) + dismiss() + } + + companion object { + fun show(fragmentManager: FragmentManager) { + val bottomSheet = AttachmentTypeSelectorBottomSheet() + bottomSheet.show(fragmentManager, "AttachmentTypeSelectorBottomSheet") + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt new file mode 100644 index 0000000000..2b85b6882a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt @@ -0,0 +1,30 @@ +/* + * 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.attachments + +import im.vector.app.core.platform.VectorSharedAction +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class AttachmentTypeSelectorSharedActionViewModel @Inject constructor() : + VectorSharedActionViewModel() + +sealed class AttachmentTypeSelectorSharedAction : VectorSharedAction { + data class SelectAttachmentTypeAction( + val attachmentType: AttachmentType + ) : AttachmentTypeSelectorSharedAction() +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index 8536b765d4..55805a0728 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -30,17 +30,11 @@ import android.view.animation.TranslateAnimation import android.widget.ImageButton import android.widget.LinearLayout import android.widget.PopupWindow -import androidx.annotation.StringRes import androidx.appcompat.widget.TooltipCompat import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.epoxy.onClick -import im.vector.app.core.utils.PERMISSIONS_EMPTY -import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING -import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT -import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO -import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_BROADCAST import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback import kotlin.math.max @@ -59,7 +53,7 @@ class AttachmentTypeSelectorView( ) : PopupWindow(context) { interface Callback { - fun onTypeSelected(type: Type) + fun onTypeSelected(type: AttachmentType) } private val views: ViewAttachmentTypeSelectorBinding @@ -69,14 +63,14 @@ class AttachmentTypeSelectorView( init { contentView = inflater.inflate(R.layout.view_attachment_type_selector, null, false) views = ViewAttachmentTypeSelectorBinding.bind(contentView) - views.attachmentGalleryButton.configure(Type.GALLERY) - views.attachmentCameraButton.configure(Type.CAMERA) - views.attachmentFileButton.configure(Type.FILE) - views.attachmentStickersButton.configure(Type.STICKER) - views.attachmentContactButton.configure(Type.CONTACT) - views.attachmentPollButton.configure(Type.POLL) - views.attachmentLocationButton.configure(Type.LOCATION) - views.attachmentVoiceBroadcast.configure(Type.VOICE_BROADCAST) + views.attachmentGalleryButton.configure(AttachmentType.GALLERY) + views.attachmentCameraButton.configure(AttachmentType.CAMERA) + views.attachmentFileButton.configure(AttachmentType.FILE) + views.attachmentStickersButton.configure(AttachmentType.STICKER) + views.attachmentContactButton.configure(AttachmentType.CONTACT) + views.attachmentPollButton.configure(AttachmentType.POLL) + views.attachmentLocationButton.configure(AttachmentType.LOCATION) + views.attachmentVoiceBroadcast.configure(AttachmentType.VOICE_BROADCAST) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -127,16 +121,16 @@ class AttachmentTypeSelectorView( } } - fun setAttachmentVisibility(type: Type, isVisible: Boolean) { + fun setAttachmentVisibility(type: AttachmentType, isVisible: Boolean) { when (type) { - Type.CAMERA -> views.attachmentCameraButton - Type.GALLERY -> views.attachmentGalleryButton - Type.FILE -> views.attachmentFileButton - Type.STICKER -> views.attachmentStickersButton - Type.CONTACT -> views.attachmentContactButton - Type.POLL -> views.attachmentPollButton - Type.LOCATION -> views.attachmentLocationButton - Type.VOICE_BROADCAST -> views.attachmentVoiceBroadcast + AttachmentType.CAMERA -> views.attachmentCameraButton + AttachmentType.GALLERY -> views.attachmentGalleryButton + AttachmentType.FILE -> views.attachmentFileButton + AttachmentType.STICKER -> views.attachmentStickersButton + AttachmentType.CONTACT -> views.attachmentContactButton + AttachmentType.POLL -> views.attachmentPollButton + AttachmentType.LOCATION -> views.attachmentLocationButton + AttachmentType.VOICE_BROADCAST -> views.attachmentVoiceBroadcast }.let { it.isVisible = isVisible } @@ -200,13 +194,13 @@ class AttachmentTypeSelectorView( return Pair(x, y) } - private fun ImageButton.configure(type: Type): ImageButton { + private fun ImageButton.configure(type: AttachmentType): ImageButton { this.setOnClickListener(TypeClickListener(type)) - TooltipCompat.setTooltipText(this, context.getString(type.tooltipRes)) + TooltipCompat.setTooltipText(this, context.getString(attachmentTooltipLabels.getValue(type))) return this } - private inner class TypeClickListener(private val type: Type) : View.OnClickListener { + private inner class TypeClickListener(private val type: AttachmentType) : View.OnClickListener { override fun onClick(v: View) { dismiss() @@ -217,14 +211,18 @@ class AttachmentTypeSelectorView( /** * The all possible types to pick with their required permissions and tooltip resource. */ - enum class Type(val permissions: List, @StringRes val tooltipRes: Int) { - CAMERA(PERMISSIONS_FOR_TAKING_PHOTO, R.string.tooltip_attachment_photo), - GALLERY(PERMISSIONS_EMPTY, R.string.tooltip_attachment_gallery), - FILE(PERMISSIONS_EMPTY, R.string.tooltip_attachment_file), - STICKER(PERMISSIONS_EMPTY, R.string.tooltip_attachment_sticker), - CONTACT(PERMISSIONS_FOR_PICKING_CONTACT, R.string.tooltip_attachment_contact), - POLL(PERMISSIONS_EMPTY, R.string.tooltip_attachment_poll), - LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING, R.string.tooltip_attachment_location), - VOICE_BROADCAST(PERMISSIONS_FOR_VOICE_BROADCAST, R.string.tooltip_attachment_voice_broadcast), + private companion object { + private val attachmentTooltipLabels: Map = AttachmentType.values().associateWith { + when (it) { + AttachmentType.CAMERA -> R.string.tooltip_attachment_photo + AttachmentType.GALLERY -> R.string.tooltip_attachment_gallery + AttachmentType.FILE -> R.string.tooltip_attachment_file + AttachmentType.STICKER -> R.string.tooltip_attachment_sticker + AttachmentType.CONTACT -> R.string.tooltip_attachment_contact + AttachmentType.POLL -> R.string.tooltip_attachment_poll + AttachmentType.LOCATION -> R.string.tooltip_attachment_location + AttachmentType.VOICE_BROADCAST -> R.string.tooltip_attachment_voice_broadcast + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt new file mode 100644 index 0000000000..ec45e226be --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt @@ -0,0 +1,59 @@ +/* + * 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.attachments + +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +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.VectorFeatures + +class AttachmentTypeSelectorViewModel @AssistedInject constructor( + @Assisted initialState: AttachmentTypeSelectorViewState, + private val vectorFeatures: VectorFeatures, +) : VectorViewModel(initialState) { + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: AttachmentTypeSelectorViewState): AttachmentTypeSelectorViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: EmptyAction) { + // do nothing + } + + init { + setState { + this.copy( + isLocationVisible = vectorFeatures.isLocationSharingEnabled(), + isVoiceBroadcastVisible = vectorFeatures.isVoiceBroadcastEnabled(), + ) + } + } +} + +data class AttachmentTypeSelectorViewState( + val isLocationVisible: Boolean = false, + val isVoiceBroadcastVisible: Boolean = false, +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt index 1a8e10d102..9692777e15 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt @@ -54,7 +54,7 @@ class AttachmentsHelper( private var captureUri: Uri? = null // The pending type is set if we have to handle permission request. It must be restored if the activity gets killed. - var pendingType: AttachmentTypeSelectorView.Type? = null + var pendingType: AttachmentType? = null // Restorable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 55ec922a57..afa9c84353 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -40,6 +40,7 @@ import androidx.core.text.buildSpannedString import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel @@ -63,6 +64,10 @@ import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentComposerBinding import im.vector.app.features.VectorFeatures +import im.vector.app.features.attachments.AttachmentType +import im.vector.app.features.attachments.AttachmentTypeSelectorBottomSheet +import im.vector.app.features.attachments.AttachmentTypeSelectorSharedAction +import im.vector.app.features.attachments.AttachmentTypeSelectorSharedActionViewModel import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.ContactAttachment @@ -92,6 +97,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData import im.vector.app.features.voice.VoiceFailure import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -161,6 +167,7 @@ class MessageComposerFragment : VectorBaseFragment(), A private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel() private lateinit var sharedActionViewModel: MessageSharedActionViewModel + private val attachmentViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels() private val composer: MessageComposerView get() { return if (vectorPreferences.isRichTextEditorEnabled()) { @@ -219,6 +226,11 @@ class MessageComposerFragment : VectorBaseFragment(), A } } + attachmentViewModel.stream() + .filterIsInstance() + .onEach { onTypeSelected(it.attachmentType) } + .launchIn(lifecycleScope) + if (savedInstanceState != null) { handleShareData() } @@ -299,21 +311,25 @@ class MessageComposerFragment : VectorBaseFragment(), A } composer.callback = object : PlainTextComposerLayout.Callback { override fun onAddAttachment() { - if (!::attachmentTypeSelector.isInitialized) { - attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.LOCATION, - vectorFeatures.isLocationSharingEnabled(), - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.VOICE_BROADCAST, - vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission - ) + if (vectorPreferences.isRichTextEditorEnabled()) { + AttachmentTypeSelectorBottomSheet.show(childFragmentManager) + } else { + if (!::attachmentTypeSelector.isInitialized) { + attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.LOCATION, + vectorFeatures.isLocationSharingEnabled(), + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.POLL, !isThreadTimeLine() + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.VOICE_BROADCAST, + vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission + ) + } + attachmentTypeSelector.show(composer.attachmentButton) } - attachmentTypeSelector.show(composer.attachmentButton) } override fun onExpandOrCompactChange() { @@ -662,20 +678,20 @@ class MessageComposerFragment : VectorBaseFragment(), A } } - private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { + private fun launchAttachmentProcess(type: AttachmentType) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( + AttachmentType.CAMERA -> attachmentsHelper.openCamera( activity = requireActivity(), vectorPreferences = vectorPreferences, cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher ) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) - AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomId, null, PollMode.CREATE) - AttachmentTypeSelectorView.Type.LOCATION -> { + AttachmentType.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentType.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) + AttachmentType.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) + AttachmentType.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentType.POLL -> navigator.openCreatePoll(requireContext(), roomId, null, PollMode.CREATE) + AttachmentType.LOCATION -> { navigator .openLocationSharing( context = requireContext(), @@ -685,11 +701,11 @@ class MessageComposerFragment : VectorBaseFragment(), A locationOwnerId = session.myUserId ) } - AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start) + AttachmentType.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start) } } - override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) { + override fun onTypeSelected(type: AttachmentType) { if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) { launchAttachmentProcess(type) } else { diff --git a/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml b/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml new file mode 100644 index 0000000000..3aec5b5c20 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt new file mode 100644 index 0000000000..478f631c06 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt @@ -0,0 +1,91 @@ +/* + * 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.attachments + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.test.fakes.FakeVectorFeatures +import im.vector.app.test.test +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +internal class AttachmentTypeSelectorViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule() + + private val fakeVectorFeatures = FakeVectorFeatures() + private val initialState = AttachmentTypeSelectorViewState() + + @Before + fun setUp() { + // Disable all features by default + fakeVectorFeatures.givenLocationSharing(isEnabled = false) + fakeVectorFeatures.givenVoiceBroadcast(isEnabled = false) + } + + @Test + fun `given features are not enabled, then options are not visible`() { + createViewModel() + .test() + .assertStates( + listOf( + initialState, + ) + ) + .finish() + } + + @Test + fun `given location sharing is enabled, then location sharing option is visible`() { + fakeVectorFeatures.givenLocationSharing(isEnabled = true) + + createViewModel() + .test() + .assertStates( + listOf( + initialState.copy( + isLocationVisible = true + ), + ) + ) + .finish() + } + + @Test + fun `given voice broadcast is enabled, then voice broadcast option is visible`() { + fakeVectorFeatures.givenVoiceBroadcast(isEnabled = true) + + createViewModel() + .test() + .assertStates( + listOf( + initialState.copy( + isVoiceBroadcastVisible = true + ), + ) + ) + .finish() + } + + private fun createViewModel(): AttachmentTypeSelectorViewModel { + return AttachmentTypeSelectorViewModel( + initialState, + vectorFeatures = fakeVectorFeatures, + ) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index 4e6b4fc3df..d989abc214 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -42,4 +42,12 @@ class FakeVectorFeatures : VectorFeatures by spyk() { fun givenCombinedLoginDisabled() { every { isOnboardingCombinedLoginEnabled() } returns false } + + fun givenLocationSharing(isEnabled: Boolean) { + every { isLocationSharingEnabled() } returns isEnabled + } + + fun givenVoiceBroadcast(isEnabled: Boolean) { + every { isVoiceBroadcastEnabled() } returns isEnabled + } } From 17c43c91888162d3c7675511ff910c46c3aa32fc Mon Sep 17 00:00:00 2001 From: Jonny Andrew Date: Thu, 20 Oct 2022 16:13:18 +0100 Subject: [PATCH 0286/1068] Add rounded corners to bottom sheet dialog. Note these are currently only visible in the collapsed state. - [Google issue](https://issuetracker.google.com/issues/144859239) - [Rejected PR](https://github.com/material-components/material-components-android/pull/437) - [Github issue](https://github.com/material-components/material-components-android/issues/1278) --- .../src/main/res/values/styles_bottom_sheet.xml | 11 +++++++++++ .../layout/bottom_sheet_attachment_type_selector.xml | 1 - .../res/layout/view_bottom_sheet_action_button.xml | 3 +-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml b/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml index f6c30040d9..7b704a6331 100644 --- a/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml +++ b/library/ui-styles/src/main/res/values/styles_bottom_sheet.xml @@ -14,6 +14,7 @@ @color/element_content_primary_light @color/element_link_light + @style/BottomSheetStyle + + + + - - - - + + diff --git a/vector/src/main/res/drawable/ic_voice_broadcast.xml b/vector/src/main/res/drawable/ic_voice_broadcast.xml new file mode 100644 index 0000000000..7d427a56d0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_voice_broadcast.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index 16a5b17d68..d508569cb0 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -11,20 +11,10 @@ @@ -78,7 +68,7 @@ android:id="@+id/voiceBroadcastMetadata" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:metadataIcon="@drawable/ic_attachment_voice_broadcast" + app:metadataIcon="@drawable/ic_voice_broadcast" app:metadataValue="@string/attachment_type_voice_broadcast" /> diff --git a/vector/src/main/res/layout/view_voice_broadcast_metadata.xml b/vector/src/main/res/layout/view_voice_broadcast_metadata.xml index 4f0c584d5c..3bc31cd9a0 100644 --- a/vector/src/main/res/layout/view_voice_broadcast_metadata.xml +++ b/vector/src/main/res/layout/view_voice_broadcast_metadata.xml @@ -15,7 +15,7 @@ android:layout_marginEnd="4dp" android:contentDescription="@null" app:tint="?vctr_content_secondary" - tools:src="@drawable/ic_attachment_voice_broadcast" /> + tools:src="@drawable/ic_voice_broadcast" /> Date: Mon, 24 Oct 2022 16:35:16 +0200 Subject: [PATCH 0340/1068] Improve VoiceBroadcastItemFactory --- .../factory/VoiceBroadcastItemFactory.kt | 48 +++++++++---------- .../timeline/helper/TimelineEventsGroups.kt | 3 ++ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 7b8c927186..b639a2dbae 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -15,14 +15,14 @@ */ package im.vector.app.features.home.room.detail.timeline.factory -import im.vector.app.core.epoxy.VectorEpoxyHolder -import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider +import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.app.features.home.room.detail.timeline.item.AbsMessageVoiceBroadcastItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem @@ -34,7 +34,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -53,31 +53,31 @@ class VoiceBroadcastItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): VectorEpoxyModel? { + ): AbsMessageVoiceBroadcastItem<*>? { // Only display item of the initial event with updated data if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null - val eventsGroup = params.eventsGroup ?: return null - val voiceBroadcastEventsGroup = VoiceBroadcastEventsGroup(eventsGroup) - val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent() - val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent() - val mostRecentMessageContent = mostRecentEvent?.content ?: return null - val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId - val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey + + val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val voiceBroadcastEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent().root.asVoiceBroadcastEvent() ?: return null + val voiceBroadcastContent = voiceBroadcastEvent.content ?: return null + val voiceBroadcastId = voiceBroadcastEventsGroup.voiceBroadcastId + + val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId + return if (isRecording) { createRecordingItem( - params.event.roomId, - eventsGroup.groupId, - mostRecentMessageContent.voiceBroadcastState, + params, + voiceBroadcastId, + voiceBroadcastContent.voiceBroadcastState, highlight, callback, attributes ) } else { createListeningItem( - params.event.roomId, - eventsGroup.groupId, - mostRecentMessageContent.voiceBroadcastState, - recorderName, + params, + voiceBroadcastId, + voiceBroadcastContent.voiceBroadcastState, highlight, callback, attributes @@ -86,14 +86,14 @@ class VoiceBroadcastItemFactory @Inject constructor( } private fun createRecordingItem( - roomId: String, + params: TimelineItemFactoryParams, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastRecordingItem { - val roomSummary = session.getRoom(roomId)?.roomSummary() + val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) @@ -109,15 +109,15 @@ class VoiceBroadcastItemFactory @Inject constructor( } private fun createListeningItem( - roomId: String, + params: TimelineItemFactoryParams, voiceBroadcastId: String, voiceBroadcastState: VoiceBroadcastState?, - broadcasterName: String?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastListeningItem { - val roomSummary = session.getRoom(roomId)?.roomSummary() + val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() + val recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName() return MessageVoiceBroadcastListeningItem_() .id("voice_broadcast_$voiceBroadcastId") .attributes(attributes) @@ -128,7 +128,7 @@ class VoiceBroadcastItemFactory @Inject constructor( .voiceBroadcastPlayer(voiceBroadcastPlayer) .voiceBroadcastId(voiceBroadcastId) .voiceBroadcastState(voiceBroadcastState) - .broadcasterName(broadcasterName) + .broadcasterName(recorderName) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index d8817c1f44..8a3be7d5f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -141,6 +141,9 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) { } class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { + + val voiceBroadcastId = group.groupId + fun getLastDisplayableEvent(): TimelineEvent { return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } ?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L } From 2c144614cabc6427319ebfcb3143ab176b6d565a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 16:49:59 +0200 Subject: [PATCH 0341/1068] Improve recording state rendering if app has been relaunched --- .../MessageVoiceBroadcastRecordingItem.kt | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index b766698851..183d2a5577 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -24,6 +24,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass @@ -32,7 +33,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem @EpoxyAttribute var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null - private lateinit var recorderListener: VoiceBroadcastRecorder.Listener + private var recorderListener: VoiceBroadcastRecorder.Listener? = null override fun bind(holder: Holder) { super.bind(holder) @@ -40,12 +41,15 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun bindVoiceBroadcastItem(holder: Holder) { - recorderListener = object : VoiceBroadcastRecorder.Listener { - override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { - renderRecordingState(holder, state) - } + if (voiceBroadcastRecorder != null && voiceBroadcastRecorder?.state != VoiceBroadcastRecorder.State.Idle) { + recorderListener = object : VoiceBroadcastRecorder.Listener { + override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { + renderRecordingState(holder, state) + } + }.also { voiceBroadcastRecorder?.addListener(it) } + } else { + renderVoiceBroadcastState(holder) } - voiceBroadcastRecorder?.addListener(recorderListener) } override fun renderMetadata(holder: Holder) { @@ -56,39 +60,54 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun renderRecordingState(holder: Holder, state: VoiceBroadcastRecorder.State) { - with(holder) { - when (state) { - VoiceBroadcastRecorder.State.Recording -> { - stopRecordButton.isEnabled = true - recordButton.isEnabled = true - - val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) - val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) - recordButton.setImageDrawable(drawable) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Paused -> { - stopRecordButton.isEnabled = true - recordButton.isEnabled = true - - recordButton.setImageResource(R.drawable.ic_recording_dot) - recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } - } - VoiceBroadcastRecorder.State.Idle -> { - recordButton.isEnabled = false - stopRecordButton.isEnabled = false - } - } + when (state) { + VoiceBroadcastRecorder.State.Recording -> renderPlayingState(holder) + VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder) + VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder) } } + private fun renderVoiceBroadcastState(holder: Holder) { + when (voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED -> renderPlayingState(holder) + VoiceBroadcastState.PAUSED -> renderPausedState(holder) + VoiceBroadcastState.STOPPED, + null -> renderStoppedState(holder) + } + } + + private fun renderPlayingState(holder: Holder) = with(holder) { + stopRecordButton.isEnabled = true + recordButton.isEnabled = true + + val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) + recordButton.setImageDrawable(drawable) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) + recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + + private fun renderPausedState(holder: Holder) = with(holder) { + stopRecordButton.isEnabled = true + recordButton.isEnabled = true + + recordButton.setImageResource(R.drawable.ic_recording_dot) + recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) + recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + } + + private fun renderStoppedState(holder: Holder) = with(holder) { + recordButton.isEnabled = false + stopRecordButton.isEnabled = false + } + override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastRecorder?.removeListener(recorderListener) + recorderListener?.let { voiceBroadcastRecorder?.removeListener(it) } + recorderListener = null } override fun getViewStubId() = STUB_ID From f31429cf25ca232344715d7a39906472e2e290be Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 15:22:16 +0200 Subject: [PATCH 0342/1068] Rename renderLiveIcon method --- .../room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index cbf35e89d2..afe705ffb6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -63,11 +63,11 @@ abstract class AbsMessageVoiceBroadcastItem Date: Tue, 25 Oct 2022 16:29:42 +0200 Subject: [PATCH 0343/1068] Move voice broadcast item attributes to dedicated class --- .../timeline/factory/MessageItemFactory.kt | 2 +- .../factory/VoiceBroadcastItemFactory.kt | 65 ++++++------------- .../item/AbsMessageVoiceBroadcastItem.kt | 42 +++++++----- .../MessageVoiceBroadcastListeningItem.kt | 17 ++--- .../MessageVoiceBroadcastRecordingItem.kt | 18 ++--- 5 files changed, 57 insertions(+), 87 deletions(-) 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 245d92f95b..f4d506fa4b 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 @@ -201,7 +201,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index b639a2dbae..d43ccd9834 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.features.displayname.getBestName -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem @@ -51,7 +50,6 @@ class VoiceBroadcastItemFactory @Inject constructor( params: TimelineItemFactoryParams, messageContent: MessageVoiceBroadcastInfoContent, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): AbsMessageVoiceBroadcastItem<*>? { // Only display item of the initial event with updated data @@ -64,72 +62,47 @@ class VoiceBroadcastItemFactory @Inject constructor( val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId + val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes( + voiceBroadcastId = voiceBroadcastId, + voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, + recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(), + recorder = voiceBroadcastRecorder, + player = voiceBroadcastPlayer, + roomItem = session.getRoom(params.event.roomId)?.roomSummary()?.toMatrixItem(), + colorProvider = colorProvider, + drawableProvider = drawableProvider, + ) + return if (isRecording) { - createRecordingItem( - params, - voiceBroadcastId, - voiceBroadcastContent.voiceBroadcastState, - highlight, - callback, - attributes - ) + createRecordingItem(highlight, attributes, voiceBroadcastAttributes) } else { - createListeningItem( - params, - voiceBroadcastId, - voiceBroadcastContent.voiceBroadcastState, - highlight, - callback, - attributes - ) + createListeningItem(highlight, attributes, voiceBroadcastAttributes) } } private fun createRecordingItem( - params: TimelineItemFactoryParams, - voiceBroadcastId: String, - voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastRecordingItem { - val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() return MessageVoiceBroadcastRecordingItem_() - .id("voice_broadcast_$voiceBroadcastId") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") .attributes(attributes) + .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) - .roomItem(roomSummary?.toMatrixItem()) - .colorProvider(colorProvider) - .drawableProvider(drawableProvider) - .voiceBroadcastRecorder(voiceBroadcastRecorder) - .voiceBroadcastId(voiceBroadcastId) - .voiceBroadcastState(voiceBroadcastState) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) } private fun createListeningItem( - params: TimelineItemFactoryParams, - voiceBroadcastId: String, - voiceBroadcastState: VoiceBroadcastState?, highlight: Boolean, - callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + voiceBroadcastAttributes: AbsMessageVoiceBroadcastItem.Attributes, ): MessageVoiceBroadcastListeningItem { - val roomSummary = session.getRoom(params.event.roomId)?.roomSummary() - val recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName() return MessageVoiceBroadcastListeningItem_() - .id("voice_broadcast_$voiceBroadcastId") + .id("voice_broadcast_${voiceBroadcastAttributes.voiceBroadcastId}") .attributes(attributes) + .voiceBroadcastAttributes(voiceBroadcastAttributes) .highlighted(highlight) - .roomItem(roomSummary?.toMatrixItem()) - .colorProvider(colorProvider) - .drawableProvider(drawableProvider) - .voiceBroadcastPlayer(voiceBroadcastPlayer) - .voiceBroadcastId(voiceBroadcastId) - .voiceBroadcastState(voiceBroadcastState) - .broadcasterName(recorderName) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index afe705ffb6..45f10b68d0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -25,29 +25,26 @@ import im.vector.app.R import im.vector.app.core.extensions.tintBackground import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.util.MatrixItem abstract class AbsMessageVoiceBroadcastItem : AbsMessageItem() { @EpoxyAttribute - var callback: TimelineEventController.Callback? = null + lateinit var voiceBroadcastAttributes: Attributes - @EpoxyAttribute - lateinit var colorProvider: ColorProvider - - @EpoxyAttribute - lateinit var drawableProvider: DrawableProvider - - @EpoxyAttribute - lateinit var voiceBroadcastId: String - - @EpoxyAttribute - var voiceBroadcastState: VoiceBroadcastState? = null - - @EpoxyAttribute - var roomItem: MatrixItem? = null + protected val voiceBroadcastId get() = voiceBroadcastAttributes.voiceBroadcastId + protected val voiceBroadcastState get() = voiceBroadcastAttributes.voiceBroadcastState + protected val recorderName get() = voiceBroadcastAttributes.recorderName + protected val recorder get() = voiceBroadcastAttributes.recorder + protected val player get() = voiceBroadcastAttributes.player + protected val roomItem get() = voiceBroadcastAttributes.roomItem + protected val colorProvider get() = voiceBroadcastAttributes.colorProvider + protected val drawableProvider get() = voiceBroadcastAttributes.drawableProvider + protected val avatarRenderer get() = attributes.avatarRenderer + protected val callback get() = attributes.callback override fun isCacheable(): Boolean = false @@ -59,7 +56,7 @@ abstract class AbsMessageVoiceBroadcastItem(R.id.roomAvatarImageView) val titleText by bind(R.id.titleText) } + + data class Attributes( + val voiceBroadcastId: String, + val voiceBroadcastState: VoiceBroadcastState?, + val recorderName: String, + val recorder: VoiceBroadcastRecorder?, + val player: VoiceBroadcastPlayer, + val roomItem: MatrixItem?, + val colorProvider: ColorProvider, + val drawableProvider: DrawableProvider, + ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 135053d9a9..d94bee3672 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.item import android.view.View import android.widget.ImageButton import androidx.core.view.isVisible -import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.onClick @@ -30,12 +29,6 @@ import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem() { - @EpoxyAttribute - var voiceBroadcastPlayer: VoiceBroadcastPlayer? = null - - @EpoxyAttribute - var broadcasterName: String? = null - private lateinit var playerListener: VoiceBroadcastPlayer.Listener override fun bind(holder: Holder) { @@ -47,12 +40,12 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem playerListener = VoiceBroadcastPlayer.Listener { state -> renderPlayingState(holder, state) } - voiceBroadcastPlayer?.addListener(voiceBroadcastId, playerListener) + player.addListener(voiceBroadcastId, playerListener) } override fun renderMetadata(holder: Holder) { with(holder) { - broadcasterNameMetadata.value = broadcasterName.orEmpty() + broadcasterNameMetadata.value = recorderName voiceBroadcastMetadata.isVisible = true listenersCountMetadata.isVisible = false } @@ -67,14 +60,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem VoiceBroadcastPlayer.State.PLAYING -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) - playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } + playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } VoiceBroadcastPlayer.State.IDLE, VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) playPauseButton.onClick { - attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) + callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } } VoiceBroadcastPlayer.State.BUFFERING -> Unit @@ -84,7 +77,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) - voiceBroadcastPlayer?.removeListener(voiceBroadcastId, playerListener) + player.removeListener(voiceBroadcastId, playerListener) } override fun getViewStubId() = STUB_ID diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 183d2a5577..47e89658ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.item import android.widget.ImageButton import androidx.core.view.isVisible -import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.onClick @@ -30,9 +29,6 @@ import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView @EpoxyModelClass abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem() { - @EpoxyAttribute - var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null - private var recorderListener: VoiceBroadcastRecorder.Listener? = null override fun bind(holder: Holder) { @@ -41,12 +37,12 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem } private fun bindVoiceBroadcastItem(holder: Holder) { - if (voiceBroadcastRecorder != null && voiceBroadcastRecorder?.state != VoiceBroadcastRecorder.State.Idle) { + if (recorder != null && recorder?.state != VoiceBroadcastRecorder.State.Idle) { recorderListener = object : VoiceBroadcastRecorder.Listener { override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { renderRecordingState(holder, state) } - }.also { voiceBroadcastRecorder?.addListener(it) } + }.also { recorder?.addListener(it) } } else { renderVoiceBroadcastState(holder) } @@ -85,8 +81,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor) recordButton.setImageDrawable(drawable) recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + recordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) } + stopRecordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } } private fun renderPausedState(holder: Holder) = with(holder) { @@ -95,8 +91,8 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem recordButton.setImageResource(R.drawable.ic_recording_dot) recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record) - recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } - stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } + recordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) } + stopRecordButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) } } private fun renderStoppedState(holder: Holder) = with(holder) { @@ -106,7 +102,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem override fun unbind(holder: Holder) { super.unbind(holder) - recorderListener?.let { voiceBroadcastRecorder?.removeListener(it) } + recorderListener?.let { recorder?.removeListener(it) } recorderListener = null } From 513097585a7ab9592eb50d5172e30eaf3c428406 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:38:05 +0200 Subject: [PATCH 0344/1068] Fix kdoc issue --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 6545948021..d8a062c8f8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -90,7 +90,7 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentRoomId: String? = null /** - * Map voiceBroadcastId to listeners + * Map voiceBroadcastId to listeners. */ private var listeners: MutableMap> = mutableMapOf() From 0f21f404e694a465cfa82a0da8e178dc9a0af821 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:41:36 +0200 Subject: [PATCH 0345/1068] Add changelog --- changelog.d/7448.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7448.wip diff --git a/changelog.d/7448.wip b/changelog.d/7448.wip new file mode 100644 index 0000000000..a99e5bbcfa --- /dev/null +++ b/changelog.d/7448.wip @@ -0,0 +1 @@ +[Voice Broadcast] Improve timeline items factory and handle bad recording state display From c7c05d1fe6a293b9233276a8b3d2c68628ecd1e3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 16:35:59 +0200 Subject: [PATCH 0346/1068] Add check on deviceId before showing recording tile --- .../room/detail/timeline/factory/VoiceBroadcastItemFactory.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index d43ccd9834..7a7cb73471 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -60,7 +60,9 @@ class VoiceBroadcastItemFactory @Inject constructor( val voiceBroadcastContent = voiceBroadcastEvent.content ?: return null val voiceBroadcastId = voiceBroadcastEventsGroup.voiceBroadcastId - val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && voiceBroadcastEvent.root.stateKey == session.myUserId + val isRecording = voiceBroadcastContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && + voiceBroadcastEvent.root.stateKey == session.myUserId && + messageContent.deviceId == session.sessionParams.deviceId val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes( voiceBroadcastId = voiceBroadcastId, From a4eff0cc78d8066ca0fa31a13028b7a81b3c31ed Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:56:27 +0200 Subject: [PATCH 0347/1068] Add changelog --- changelog.d/7431.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7431.bugfix diff --git a/changelog.d/7431.bugfix b/changelog.d/7431.bugfix new file mode 100644 index 0000000000..681a1e9aa5 --- /dev/null +++ b/changelog.d/7431.bugfix @@ -0,0 +1 @@ + [Voice Broadcast] Do not display the recorder view for a live broadcast started from another session \ No newline at end of file From 6eeb54ae40dbd995a55822659c40365d288e0727 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 24 Oct 2022 23:59:29 +0200 Subject: [PATCH 0348/1068] Stop ongoing voice broadcast on app restart --- .../features/home/HomeActivityViewModel.kt | 30 +++++++++++++ .../voicebroadcast/VoiceBroadcastHelper.kt | 4 ++ .../usecase/GetLastVoiceBroadcastUseCase.kt | 45 +++++++++++++++++++ .../usecase/StartVoiceBroadcastUseCase.kt | 10 +---- 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 61a8e5b79e..1e79dc5844 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,6 +42,8 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -60,12 +62,14 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -92,6 +96,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val vectorFeatures: VectorFeatures, + private val voiceBroadcastHelper: VoiceBroadcastHelper, ) : VectorViewModel(initialState) { @AssistedFactory @@ -123,6 +128,7 @@ class HomeActivityViewModel @AssistedInject constructor( observeReleaseNotes() observeLocalNotificationsSilenced() initThreadsMigration() + stopOngoingVoiceBroadcast() } private fun observeReleaseNotes() = withState { state -> @@ -490,6 +496,30 @@ class HomeActivityViewModel @AssistedInject constructor( } } + /** + * Stop ongoing voice broadcast if any. + */ + private fun stopOngoingVoiceBroadcast() { + val session = activeSessionHolder.getSafeActiveSession() ?: return + + // FIXME Iterate only on recent rooms for the moment, improve this + val recentRooms = session.roomService().getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }).mapNotNull { session.getRoom(it.roomId) } + + recentRooms + .forEach { room -> + val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId + val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } + if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { + viewModelScope.launch { voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } + return // No need to iterate more as we should not have more than one recording VB + } + } + } + override fun handle(action: HomeActivityViewActions) { when (action) { HomeActivityViewActions.PushPromptHasBeenReviewed -> { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index 58e7de7f32..ee9034661c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast +import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase @@ -30,6 +31,7 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -45,4 +47,6 @@ class VoiceBroadcastHelper @Inject constructor( fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() + + fun getOngoingVoiceBroadcasts(roomId: String) = getOngoingVoiceBroadcastsUseCase.execute(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..db2c625161 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt @@ -0,0 +1,45 @@ +/* + * 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.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import timber.log.Timber +import javax.inject.Inject + +class GetOngoingVoiceBroadcastsUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(roomId: String): List { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") + + return room.stateService().getStateEvents( + setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), + QueryStringValue.IsNotEmpty + ) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7934d18e36..2b7ca7b9f1 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -25,9 +25,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState -import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.lib.multipicker.utils.toMultiPickerAudioType -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent @@ -43,6 +41,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val context: Context, private val buildMeta: BuildMeta, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -50,12 +49,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( - setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), - QueryStringValue.IsNotEmpty - ) - .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + val onGoingVoiceBroadcastEvents = getOngoingVoiceBroadcastsUseCase.execute(roomId) if (onGoingVoiceBroadcastEvents.isEmpty()) { startVoiceBroadcast(room) From 53db04c8cf28d3b3ce2993367f1df986eccad962 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 25 Oct 2022 17:58:09 +0200 Subject: [PATCH 0349/1068] Add changelog --- changelog.d/7450.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7450.wip diff --git a/changelog.d/7450.wip b/changelog.d/7450.wip new file mode 100644 index 0000000000..de4d3dc5e1 --- /dev/null +++ b/changelog.d/7450.wip @@ -0,0 +1 @@ +[Voice Broadcast] Stop recording when opening the room after an app restart From 85bc78bd72f15b2741b02b3a6f8389e0d4474761 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 09:50:58 +0200 Subject: [PATCH 0350/1068] Do not pause already paused voice broadcast --- .../home/room/detail/composer/MessageComposerFragment.kt | 2 +- .../home/room/detail/composer/MessageComposerViewState.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 55ec922a57..e01dd31516 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -234,7 +234,7 @@ class MessageComposerFragment : VectorBaseFragment(), A } // TODO remove this when there will be a recording indicator outside of the timeline // Pause voice broadcast if the timeline is not shown anymore - it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) + it.isRecordingVoiceBroadcast && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause) else -> { timelineViewModel.handle(VoiceBroadcastAction.Listening.Pause) messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) 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 0df1dbebd8..a4021f87b2 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 @@ -79,9 +79,8 @@ data class MessageComposerViewState( is VoiceMessageRecorderView.RecordingUiState.Recording -> true } - val isVoiceBroadcasting = when (voiceBroadcastState) { + val isRecordingVoiceBroadcast = when (voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.PAUSED, VoiceBroadcastState.RESUMED -> true else -> false } From 47047b20349c423b6f070e78797ffbbd599d764c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:00:56 +0200 Subject: [PATCH 0351/1068] move map operator in a new line --- .../vector/app/features/home/HomeActivityViewModel.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 1e79dc5844..2c45709291 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -503,10 +503,12 @@ class HomeActivityViewModel @AssistedInject constructor( val session = activeSessionHolder.getSafeActiveSession() ?: return // FIXME Iterate only on recent rooms for the moment, improve this - val recentRooms = session.roomService().getBreadcrumbs(roomSummaryQueryParams { - displayName = QueryStringValue.NoCondition - memberships = listOf(Membership.JOIN) - }).mapNotNull { session.getRoom(it.roomId) } + val recentRooms = session.roomService() + .getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + .mapNotNull { session.getRoom(it.roomId) } recentRooms .forEach { room -> From ec80adc8aa416c07ebd61faff5cab5c26e7ccff4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:10:56 +0200 Subject: [PATCH 0352/1068] Rename usecase file --- ...iceBroadcastUseCase.kt => GetOngoingVoiceBroadcastsUseCase.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/{GetLastVoiceBroadcastUseCase.kt => GetOngoingVoiceBroadcastsUseCase.kt} (100%) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetLastVoiceBroadcastUseCase.kt rename to vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt From 6091ec4ce3731b6498c42860d1d82dce677c2a8d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:45:25 +0200 Subject: [PATCH 0353/1068] Fix wrong content description --- .../timeline/item/MessageVoiceBroadcastListeningItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index d94bee3672..a3e7cc55d5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -59,13 +59,13 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem when (state) { VoiceBroadcastPlayer.State.PLAYING -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_pause) - playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) } } VoiceBroadcastPlayer.State.IDLE, VoiceBroadcastPlayer.State.PAUSED -> { playPauseButton.setImageResource(R.drawable.ic_play_pause_play) - playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast) + playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast) playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) } From 8fe3b5e75077d21b32cf6c2ba82fc7c9e47200d7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:46:33 +0200 Subject: [PATCH 0354/1068] Rename method renderPlayingState to renderRecordingState --- .../timeline/item/MessageVoiceBroadcastRecordingItem.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt index 47e89658ca..e3e86f38e3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt @@ -57,7 +57,7 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderRecordingState(holder: Holder, state: VoiceBroadcastRecorder.State) { when (state) { - VoiceBroadcastRecorder.State.Recording -> renderPlayingState(holder) + VoiceBroadcastRecorder.State.Recording -> renderRecordingState(holder) VoiceBroadcastRecorder.State.Paused -> renderPausedState(holder) VoiceBroadcastRecorder.State.Idle -> renderStoppedState(holder) } @@ -66,14 +66,14 @@ abstract class MessageVoiceBroadcastRecordingItem : AbsMessageVoiceBroadcastItem private fun renderVoiceBroadcastState(holder: Holder) { when (voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> renderPlayingState(holder) + VoiceBroadcastState.RESUMED -> renderRecordingState(holder) VoiceBroadcastState.PAUSED -> renderPausedState(holder) VoiceBroadcastState.STOPPED, null -> renderStoppedState(holder) } } - private fun renderPlayingState(holder: Holder) = with(holder) { + private fun renderRecordingState(holder: Holder) = with(holder) { stopRecordButton.isEnabled = true recordButton.isEnabled = true From 1554d79f1a57095f3f98b20ffee8e944e7d60374 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 10:48:11 +0200 Subject: [PATCH 0355/1068] Change listeners Map variable to immutable --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index d8a062c8f8..5a04904f69 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -92,7 +92,7 @@ class VoiceBroadcastPlayer @Inject constructor( /** * Map voiceBroadcastId to listeners. */ - private var listeners: MutableMap> = mutableMapOf() + private val listeners: MutableMap> = mutableMapOf() fun playOrResume(roomId: String, eventId: String) { val hasChanged = currentVoiceBroadcastId != eventId From b7e0d93ce135ce0c4a6c8b4ca4216dc9375e6142 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Tue, 25 Oct 2022 13:50:22 +0100 Subject: [PATCH 0356/1068] Do not require kittykat's PR to include a sign-off --- tools/danger/dangerfile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js index 2fc55bad0c..d0b4e6d300 100644 --- a/tools/danger/dangerfile.js +++ b/tools/danger/dangerfile.js @@ -82,6 +82,7 @@ const allowList = [ "ganfra", "jmartinesp", "jonnyandrew", + "kittykat", "langleyd", "MadLittleMods", "manuroe", From b532112f584a0cbeebf2b943bb9a3812739bb643 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 15:14:02 +0200 Subject: [PATCH 0357/1068] Adding changelog entry --- changelog.d/7396.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7396.feature diff --git a/changelog.d/7396.feature b/changelog.d/7396.feature new file mode 100644 index 0000000000..8ce14eb3d3 --- /dev/null +++ b/changelog.d/7396.feature @@ -0,0 +1 @@ +Multi selection in sessions list From 2e155b1acc316df93a71cfa9794b92cde5494696 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 16:24:04 +0200 Subject: [PATCH 0358/1068] Toggling of selectMode using menu i OtherSessionsFragment --- .../src/main/res/values/strings.xml | 7 ++ .../v2/othersessions/OtherSessionsAction.kt | 2 + .../v2/othersessions/OtherSessionsFragment.kt | 65 ++++++++++++++++++- .../othersessions/OtherSessionsViewModel.kt | 11 ++++ .../othersessions/OtherSessionsViewState.kt | 1 + .../src/main/res/menu/menu_other_sessions.xml | 21 ++++++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/menu/menu_other_sessions.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d5223a0638..7c74162859 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3328,6 +3328,13 @@ No unverified sessions found. No inactive sessions found. Clear Filter + Select sessions + Select all + Deselect all + + %1$d selected + %1$d selected + Sign out of this session Session details Application, device, and activity information. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index 7164ecc866..2d184ffeaa 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -21,4 +21,6 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType sealed class OtherSessionsAction : VectorViewModelAction { data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction() + object EnableSelectMode : OtherSessionsAction() + object DisableSelectMode : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 610776e22e..804344e00e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -18,8 +18,12 @@ package im.vector.app.features.settings.devices.v2.othersessions import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import androidx.annotation.StringRes import androidx.core.view.isVisible import com.airbnb.mvrx.Success @@ -31,7 +35,9 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.platform.VectorMenuProvider import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentOtherSessionsBinding import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet @@ -46,19 +52,65 @@ import javax.inject.Inject class OtherSessionsFragment : VectorBaseFragment(), VectorBaseBottomSheetDialogFragment.ResultListener, - OtherSessionsView.Callback { + OtherSessionsView.Callback, + VectorMenuProvider { private val viewModel: OtherSessionsViewModel by fragmentViewModel() private val args: OtherSessionsArgs by args() @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var stringProvider: StringProvider + @Inject lateinit var viewNavigator: OtherSessionsViewNavigator override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding { return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false) } + override fun getMenuRes() = R.menu.menu_other_sessions + + override fun handlePrepareMenu(menu: Menu) { + withState(viewModel) { state -> + val isSelectModeEnabled = state.isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled + menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled + } + } + + override fun handleMenuItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.otherSessionsSelect -> { + enableSelectMode(true) + true + } + else -> false + } + } + + // TODO call enableSelectMode(true) on long press of an item when disabled + private fun enableSelectMode(isEnabled: Boolean) { + val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode + viewModel.handle(action) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activity?.onBackPressedDispatcher?.addCallback(owner = this) { + handleBackPress(this) + } + } + + private fun handleBackPress(onBackPressedCallback: OnBackPressedCallback) = withState(viewModel) { state -> + if (state.isSelectModeEnabled) { + enableSelectMode(false) + } else { + onBackPressedCallback.isEnabled = false + activity?.onBackPressedDispatcher?.onBackPressed() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar(views.otherSessionsToolbar).setTitle(args.titleResourceId).allowBack() @@ -102,11 +154,22 @@ class OtherSessionsFragment : } override fun invalidate() = withState(viewModel) { state -> + updateToolbar(state.isSelectModeEnabled) if (state.devices is Success) { renderDevices(state.devices(), state.currentFilter) } } + private fun updateToolbar(isSelectModeEnabled: Boolean) { + invalidateOptionsMenu() + val title = if (isSelectModeEnabled) { + stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, 0, 0) + } else { + getString(args.titleResourceId) + } + toolbar?.title = title + } + private fun renderDevices(devices: List?, currentFilter: DeviceManagerFilterType) { views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index e52953e2b6..21291fc082 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -64,9 +64,12 @@ class OtherSessionsViewModel @AssistedInject constructor( } } + // TODO update unit tests override fun handle(action: OtherSessionsAction) { when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) + OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() + OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode() } } @@ -78,4 +81,12 @@ class OtherSessionsViewModel @AssistedInject constructor( } observeDevices(action.filterType) } + + private fun handleDisableSelectMode() { + setState { copy(isSelectModeEnabled = false) } + } + + private fun handleEnableSelectMode() { + setState { copy(isSelectModeEnabled = true) } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt index 5256a9b27a..0db3c8cd0e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt @@ -26,6 +26,7 @@ data class OtherSessionsViewState( val devices: Async> = Uninitialized, val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS, val excludeCurrentDevice: Boolean = false, + val isSelectModeEnabled: Boolean = false, ) : MavericksState { constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice) diff --git a/vector/src/main/res/menu/menu_other_sessions.xml b/vector/src/main/res/menu/menu_other_sessions.xml new file mode 100644 index 0000000000..0edab99127 --- /dev/null +++ b/vector/src/main/res/menu/menu_other_sessions.xml @@ -0,0 +1,21 @@ + + + + + + + + + From ab2e91ae80fd33c12eef06e0ae9a88acf2b346e2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 18 Oct 2022 17:06:39 +0200 Subject: [PATCH 0359/1068] Enable selectMode when long pressing on list item --- .../settings/devices/v2/VectorSettingsDevicesFragment.kt | 4 ++++ .../features/settings/devices/v2/list/OtherSessionItem.kt | 5 +++++ .../settings/devices/v2/list/OtherSessionsController.kt | 6 ++++++ .../features/settings/devices/v2/list/OtherSessionsView.kt | 5 +++++ .../devices/v2/othersessions/OtherSessionsFragment.kt | 7 ++++++- 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index c507699e0b..1c348af4f9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -331,6 +331,10 @@ class VectorSettingsDevicesFragment : views.waitingView.root.isVisible = isLoading } + override fun onOtherSessionLongClicked(deviceId: String) { + // do nothing + } + override fun onOtherSessionClicked(deviceId: String) { navigateToSessionOverview(deviceId) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index f83f069a9f..6397a42f7f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.devices.v2.list import android.graphics.drawable.Drawable +import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt @@ -59,11 +60,15 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onLongClickListener: OnLongClickListener? = null + private val setDeviceTypeIconUseCase = SetDeviceTypeIconUseCase() override fun bind(holder: Holder) { super.bind(holder) holder.view.onClick(clickListener) + holder.view.setOnLongClickListener(onLongClickListener) if (clickListener == null) { holder.view.isClickable = false } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index 59e7e1888e..d7166d02d2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devices.v2.list +import android.view.View import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.R import im.vector.app.core.date.DateFormatKind @@ -38,6 +39,7 @@ class OtherSessionsController @Inject constructor( var callback: Callback? = null interface Callback { + fun onItemLongClicked(deviceId: String) fun onItemClicked(deviceId: String) } @@ -72,6 +74,10 @@ class OtherSessionsController @Inject constructor( sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } + onLongClickListener(View.OnLongClickListener { + device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) } + true + }) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt index 6f6956c885..a4f8bb64db 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt @@ -40,6 +40,7 @@ class OtherSessionsView @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr), OtherSessionsController.Callback { interface Callback { + fun onOtherSessionLongClicked(deviceId: String) fun onOtherSessionClicked(deviceId: String) fun onViewAllOtherSessionsClicked() } @@ -107,4 +108,8 @@ class OtherSessionsView @JvmOverloads constructor( override fun onItemClicked(deviceId: String) { callback?.onOtherSessionClicked(deviceId) } + + override fun onItemLongClicked(deviceId: String) { + callback?.onOtherSessionLongClicked(deviceId) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 804344e00e..77aac4d91d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -89,7 +89,6 @@ class OtherSessionsFragment : } } - // TODO call enableSelectMode(true) on long press of an item when disabled private fun enableSelectMode(isEnabled: Boolean) { val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode viewModel.handle(action) @@ -253,6 +252,12 @@ class OtherSessionsFragment : SessionLearnMoreBottomSheet.show(childFragmentManager, args) } + override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state -> + if (!state.isSelectModeEnabled) { + enableSelectMode(true) + } + } + override fun onOtherSessionClicked(deviceId: String) { viewNavigator.navigateToSessionOverview( context = requireActivity(), From 5b1bf8a68eca5a6a3442e22bb5e171d1928dcf60 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 11:40:43 +0200 Subject: [PATCH 0360/1068] Select devices with basic UI for tests --- .../settings/devices/v2/DeviceFullInfo.kt | 1 + .../devices/v2/list/OtherSessionItem.kt | 9 +++++ .../v2/list/OtherSessionsController.kt | 1 + .../v2/othersessions/OtherSessionsAction.kt | 3 +- .../v2/othersessions/OtherSessionsFragment.kt | 34 ++++++++++------- .../othersessions/OtherSessionsViewModel.kt | 35 +++++++++++++++-- .../main/res/layout/item_other_session.xml | 38 +++++++++++++------ .../main/res/layout/view_other_sessions.xml | 2 +- 8 files changed, 93 insertions(+), 30 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt index a47ea7e917..4864c41394 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt @@ -30,4 +30,5 @@ data class DeviceFullInfo( val isCurrentDevice: Boolean, val deviceExtendedInfo: DeviceExtendedInfo, val matrixClientInfo: MatrixClientInfoContent?, + val isSelected: Boolean = false, ) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index 6397a42f7f..888305fb4e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -17,10 +17,12 @@ package im.vector.app.features.settings.devices.v2.list import android.graphics.drawable.Drawable +import android.view.View import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -57,6 +59,9 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute lateinit var stringProvider: StringProvider + @EpoxyAttribute + var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null @@ -81,6 +86,9 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la holder.otherSessionDescriptionTextView.setTextColor(it) } holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null) + // TODO set drawable with correct color and corners + val color = if (selected) R.color.alert_default_error_background else android.R.color.transparent + holder.otherSessionItemBackgroundView.setBackgroundColor(ContextCompat.getColor(holder.view.context, color)) } class Holder : VectorEpoxyHolder() { @@ -88,5 +96,6 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la val otherSessionVerificationStatusImageView by bind(R.id.otherSessionVerificationStatusImageView) val otherSessionNameTextView by bind(R.id.otherSessionNameTextView) val otherSessionDescriptionTextView by bind(R.id.otherSessionDescriptionTextView) + val otherSessionItemBackgroundView by bind(R.id.otherSessionItemBackground) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index d7166d02d2..b50abaaf7d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -73,6 +73,7 @@ class OtherSessionsController @Inject constructor( sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) + selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemLongClicked(it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index 2d184ffeaa..becac467ec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -21,6 +21,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType sealed class OtherSessionsAction : VectorViewModelAction { data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction() - object EnableSelectMode : OtherSessionsAction() + data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction() object DisableSelectMode : OtherSessionsAction() + data class ToggleSelectionForDevice(val deviceId: String) : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 77aac4d91d..1a0d63f04a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -89,8 +89,8 @@ class OtherSessionsFragment : } } - private fun enableSelectMode(isEnabled: Boolean) { - val action = if (isEnabled) OtherSessionsAction.EnableSelectMode else OtherSessionsAction.DisableSelectMode + private fun enableSelectMode(isEnabled: Boolean, deviceId: String? = null) { + val action = if (isEnabled) OtherSessionsAction.EnableSelectMode(deviceId) else OtherSessionsAction.DisableSelectMode viewModel.handle(action) } @@ -153,23 +153,25 @@ class OtherSessionsFragment : } override fun invalidate() = withState(viewModel) { state -> - updateToolbar(state.isSelectModeEnabled) if (state.devices is Success) { - renderDevices(state.devices(), state.currentFilter) + val devices = state.devices().orEmpty() + renderDevices(devices, state.currentFilter) + updateToolbar(devices, state.isSelectModeEnabled) } } - private fun updateToolbar(isSelectModeEnabled: Boolean) { + private fun updateToolbar(devices: List, isSelectModeEnabled: Boolean) { invalidateOptionsMenu() val title = if (isSelectModeEnabled) { - stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, 0, 0) + val selection = devices.count { it.isSelected } + stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, selection, selection) } else { getString(args.titleResourceId) } toolbar?.title = title } - private fun renderDevices(devices: List?, currentFilter: DeviceManagerFilterType) { + private fun renderDevices(devices: List, currentFilter: DeviceManagerFilterType) { views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS @@ -222,7 +224,7 @@ class OtherSessionsFragment : } } - if (devices.isNullOrEmpty()) { + if (devices.isEmpty()) { views.deviceListOtherSessions.isVisible = false views.otherSessionsNotFoundLayout.isVisible = true } else { @@ -254,15 +256,19 @@ class OtherSessionsFragment : override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state -> if (!state.isSelectModeEnabled) { - enableSelectMode(true) + enableSelectMode(true, deviceId) } } - override fun onOtherSessionClicked(deviceId: String) { - viewNavigator.navigateToSessionOverview( - context = requireActivity(), - deviceId = deviceId - ) + override fun onOtherSessionClicked(deviceId: String) = withState(viewModel) { state -> + if (state.isSelectModeEnabled) { + viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(deviceId)) + } else { + viewNavigator.navigateToSessionOverview( + context = requireActivity(), + deviceId = deviceId + ) + } } override fun onViewAllOtherSessionsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 21291fc082..4a00f0ab2b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.devices.v2.othersessions import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.Success import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -69,7 +70,8 @@ class OtherSessionsViewModel @AssistedInject constructor( when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() - OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode() + is OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode(action.deviceId) + is OtherSessionsAction.ToggleSelectionForDevice -> handleToggleSelectionForDevice(action.deviceId) } } @@ -83,10 +85,37 @@ class OtherSessionsViewModel @AssistedInject constructor( } private fun handleDisableSelectMode() { + // TODO deselect all selected sessions setState { copy(isSelectModeEnabled = false) } } - private fun handleEnableSelectMode() { - setState { copy(isSelectModeEnabled = true) } + private fun handleEnableSelectMode(deviceId: String?) { + toggleSelectionForDevice(deviceId, true) + } + + private fun handleToggleSelectionForDevice(deviceId: String) = withState { state -> + toggleSelectionForDevice(deviceId, state.isSelectModeEnabled) + } + + private fun toggleSelectionForDevice(deviceId: String?, enableSelectMode: Boolean) = withState { state -> + val updatedDevices = if (state.devices is Success) { + val devices = state.devices.invoke().toMutableList() + val indexToUpdate = devices.indexOfFirst { it.deviceInfo.deviceId == deviceId } + if (indexToUpdate >= 0) { + val currentInfo = devices[indexToUpdate] + val updatedInfo = currentInfo.copy(isSelected = !currentInfo.isSelected) + devices[indexToUpdate] = updatedInfo + } + Success(devices) + } else { + state.devices + } + + setState { + copy( + devices = updatedDevices, + isSelectModeEnabled = enableSelectMode + ) + } } } diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 2f93c2be5d..29f6fafcbc 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -5,30 +5,44 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?selectableItemBackground" - android:paddingTop="16dp"> + android:paddingTop="8dp" + android:paddingHorizontal="8dp"> + + @@ -59,10 +75,10 @@ + app:layout_constraintTop_toBottomOf="@+id/otherSessionItemBackground" /> diff --git a/vector/src/main/res/layout/view_other_sessions.xml b/vector/src/main/res/layout/view_other_sessions.xml index aacbbe8ffe..2d02870174 100644 --- a/vector/src/main/res/layout/view_other_sessions.xml +++ b/vector/src/main/res/layout/view_other_sessions.xml @@ -9,7 +9,6 @@ android:id="@+id/otherSessionsRecyclerView" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -21,6 +20,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="0dp" + android:layout_marginStart="16dp" app:layout_constraintStart_toStartOf="@id/otherSessionsRecyclerView" app:layout_constraintTop_toBottomOf="@id/otherSessionsRecyclerView" tools:text="@string/device_manager_other_sessions_view_all" /> From 2fc2665ff31c994c334522638d0d1b036b4852e2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 12:51:59 +0200 Subject: [PATCH 0361/1068] Deselect all sessions when leaving select mode --- .../v2/othersessions/OtherSessionsFragment.kt | 2 +- .../othersessions/OtherSessionsViewModel.kt | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 1a0d63f04a..73e528b358 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -154,7 +154,7 @@ class OtherSessionsFragment : override fun invalidate() = withState(viewModel) { state -> if (state.devices is Success) { - val devices = state.devices().orEmpty() + val devices = state.devices.invoke() renderDevices(devices, state.currentFilter) updateToolbar(devices, state.isSelectModeEnabled) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 4a00f0ab2b..4ad2ab96e8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -85,16 +85,15 @@ class OtherSessionsViewModel @AssistedInject constructor( } private fun handleDisableSelectMode() { - // TODO deselect all selected sessions - setState { copy(isSelectModeEnabled = false) } + setSelectionForAllDevices(isSelected = false, enableSelectMode = false) } private fun handleEnableSelectMode(deviceId: String?) { - toggleSelectionForDevice(deviceId, true) + toggleSelectionForDevice(deviceId, enableSelectMode = true) } private fun handleToggleSelectionForDevice(deviceId: String) = withState { state -> - toggleSelectionForDevice(deviceId, state.isSelectModeEnabled) + toggleSelectionForDevice(deviceId, enableSelectMode = state.isSelectModeEnabled) } private fun toggleSelectionForDevice(deviceId: String?, enableSelectMode: Boolean) = withState { state -> @@ -118,4 +117,20 @@ class OtherSessionsViewModel @AssistedInject constructor( ) } } + + private fun setSelectionForAllDevices(isSelected: Boolean, enableSelectMode: Boolean) = withState { state -> + val updatedDevices = if (state.devices is Success) { + val updatedDevices = state.devices.invoke().map { it.copy(isSelected = isSelected) } + Success(updatedDevices) + } else { + state.devices + } + + setState { + copy( + devices = updatedDevices, + isSelectModeEnabled = enableSelectMode + ) + } + } } From a703b8ae1015e0335a6444dc1f5a9af770dbaee3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 14:16:31 +0200 Subject: [PATCH 0362/1068] Select all/Deselect all actions --- .../devices/v2/othersessions/OtherSessionsAction.kt | 2 ++ .../devices/v2/othersessions/OtherSessionsFragment.kt | 8 ++++++++ .../devices/v2/othersessions/OtherSessionsViewModel.kt | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt index becac467ec..1978708ebf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt @@ -24,4 +24,6 @@ sealed class OtherSessionsAction : VectorViewModelAction { data class EnableSelectMode(val deviceId: String?) : OtherSessionsAction() object DisableSelectMode : OtherSessionsAction() data class ToggleSelectionForDevice(val deviceId: String) : OtherSessionsAction() + object SelectAll : OtherSessionsAction() + object DeselectAll : OtherSessionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 73e528b358..958266631e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -85,6 +85,14 @@ class OtherSessionsFragment : enableSelectMode(true) true } + R.id.otherSessionsSelectAll -> { + viewModel.handle(OtherSessionsAction.SelectAll) + true + } + R.id.otherSessionsDeselectAll -> { + viewModel.handle(OtherSessionsAction.DeselectAll) + true + } else -> false } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 4ad2ab96e8..e4c1b98288 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -72,6 +72,8 @@ class OtherSessionsViewModel @AssistedInject constructor( OtherSessionsAction.DisableSelectMode -> handleDisableSelectMode() is OtherSessionsAction.EnableSelectMode -> handleEnableSelectMode(action.deviceId) is OtherSessionsAction.ToggleSelectionForDevice -> handleToggleSelectionForDevice(action.deviceId) + OtherSessionsAction.DeselectAll -> handleDeselectAll() + OtherSessionsAction.SelectAll -> handleSelectAll() } } @@ -118,6 +120,14 @@ class OtherSessionsViewModel @AssistedInject constructor( } } + private fun handleSelectAll() = withState { state -> + setSelectionForAllDevices(isSelected = true, enableSelectMode = state.isSelectModeEnabled) + } + + private fun handleDeselectAll() = withState { state -> + setSelectionForAllDevices(isSelected = false, enableSelectMode = state.isSelectModeEnabled) + } + private fun setSelectionForAllDevices(isSelected: Boolean, enableSelectMode: Boolean) = withState { state -> val updatedDevices = if (state.devices is Success) { val updatedDevices = state.devices.invoke().map { it.copy(isSelected = isSelected) } From 3390d7fde493101d1c0b2662210f666de467facc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 15:32:09 +0200 Subject: [PATCH 0363/1068] Handling correct UI for selected session --- .../devices/v2/list/OtherSessionItem.kt | 24 ++++++++++++++----- .../v2/list/OtherSessionsController.kt | 2 ++ .../src/main/res/drawable/bg_device_type.xml | 18 +++++++++----- .../main/res/drawable/bg_other_session.xml | 9 +++++++ .../main/res/layout/item_other_session.xml | 1 + 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/res/drawable/bg_other_session.xml diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index 888305fb4e..de1cd33d35 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -22,7 +22,6 @@ import android.view.View.OnLongClickListener import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt -import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -30,6 +29,8 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.views.ShieldImageView import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel @@ -59,6 +60,12 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la @EpoxyAttribute lateinit var stringProvider: StringProvider + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + + @EpoxyAttribute + lateinit var drawableProvider: DrawableProvider + @EpoxyAttribute var selected: Boolean = false @@ -74,11 +81,18 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la super.bind(holder) holder.view.onClick(clickListener) holder.view.setOnLongClickListener(onLongClickListener) - if (clickListener == null) { + if (clickListener == null && onLongClickListener == null) { holder.view.isClickable = false } - setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider) + holder.otherSessionDeviceTypeImageView.isSelected = selected + if (selected) { + val drawableColor = colorProvider.getColorFromAttribute(android.R.attr.colorBackground) + val drawable = drawableProvider.getDrawable(R.drawable.ic_check_on, drawableColor) + holder.otherSessionDeviceTypeImageView.setImageDrawable(drawable) + } else { + setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider) + } holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel) holder.otherSessionNameTextView.text = sessionName holder.otherSessionDescriptionTextView.text = sessionDescription @@ -86,9 +100,7 @@ abstract class OtherSessionItem : VectorEpoxyModel(R.la holder.otherSessionDescriptionTextView.setTextColor(it) } holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null) - // TODO set drawable with correct color and corners - val color = if (selected) R.color.alert_default_error_background else android.R.color.transparent - holder.otherSessionItemBackgroundView.setBackgroundColor(ContextCompat.getColor(holder.view.context, color)) + holder.otherSessionItemBackgroundView.isSelected = selected } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index b50abaaf7d..9193479b74 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -73,6 +73,8 @@ class OtherSessionsController @Inject constructor( sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) stringProvider(this@OtherSessionsController.stringProvider) + colorProvider(this@OtherSessionsController.colorProvider) + drawableProvider(this@OtherSessionsController.drawableProvider) selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { diff --git a/vector/src/main/res/drawable/bg_device_type.xml b/vector/src/main/res/drawable/bg_device_type.xml index 88a90ccbe6..dc85b723a8 100644 --- a/vector/src/main/res/drawable/bg_device_type.xml +++ b/vector/src/main/res/drawable/bg_device_type.xml @@ -1,7 +1,13 @@ - - - - - + + + + + + + + + + + + diff --git a/vector/src/main/res/drawable/bg_other_session.xml b/vector/src/main/res/drawable/bg_other_session.xml new file mode 100644 index 0000000000..a0c988b8a2 --- /dev/null +++ b/vector/src/main/res/drawable/bg_other_session.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 29f6fafcbc..908a0e0073 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -12,6 +12,7 @@ android:id="@+id/otherSessionItemBackground" android:layout_width="0dp" android:layout_height="0dp" + android:background="@drawable/bg_other_session" app:layout_constraintBottom_toBottomOf="@id/otherSessionVerificationStatusImageView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From 4a8289c6cc7d667324ebaea495c7270408069559 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 16:19:57 +0200 Subject: [PATCH 0364/1068] Adding first unit test on OtherSessionsViewModel for init of ViewModel --- .../othersessions/OtherSessionsViewModel.kt | 1 - .../OtherSessionsViewModelTest.kt | 114 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index e4c1b98288..2cd0c6af66 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -65,7 +65,6 @@ class OtherSessionsViewModel @AssistedInject constructor( } } - // TODO update unit tests override fun handle(action: OtherSessionsAction) { when (action) { is OtherSessionsAction.FilterDevices -> handleFilterDevices(action) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt new file mode 100644 index 0000000000..c1018003a5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -0,0 +1,114 @@ +/* + * 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.settings.devices.v2.othersessions + +import android.os.SystemClock +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase +import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase +import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeVerificationService +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verifyAll +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +private const val A_TITLE_RES_ID = 1 + +class OtherSessionsViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val defaultArgs = OtherSessionsArgs( + titleResourceId = A_TITLE_RES_ID, + defaultFilter = DeviceManagerFilterType.ALL_SESSIONS, + excludeCurrentDevice = false, + ) + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + private val fakeGetDeviceFullInfoListUseCase = mockk() + private val fakeRefreshDevicesUseCaseUseCase = mockk() + + private fun createViewModel(args: OtherSessionsArgs = defaultArgs) = OtherSessionsViewModel( + initialState = OtherSessionsViewState(args), + activeSessionHolder = fakeActiveSessionHolder.instance, + getDeviceFullInfoListUseCase = fakeGetDeviceFullInfoListUseCase, + refreshDevicesUseCase = fakeRefreshDevicesUseCaseUseCase, + ) + + @Before + fun setup() { + // Needed for internal usage of Flow.throttleFirst() inside the ViewModel + mockkStatic(SystemClock::class) + every { SystemClock.elapsedRealtime() } returns 1234 + + givenVerificationService() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given the viewModel has been initialized then viewState is updated with devices list`() { + // Given + val devices = mockk>() + givenGetDeviceFullInfoListReturns(devices) + val expectedState = OtherSessionsViewState( + devices = Success(devices), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + + // Then + viewModel.test() + .assertLatestState { state -> state == expectedState } + .finish() + verifyAll { fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) } + } + + private fun givenGetDeviceFullInfoListReturns(devices: List) { + every { fakeGetDeviceFullInfoListUseCase.execute(any(), any()) } returns flowOf(devices) + } + + private fun givenVerificationService(): FakeVerificationService { + val fakeVerificationService = fakeActiveSessionHolder + .fakeSession + .fakeCryptoService + .fakeVerificationService + fakeVerificationService.givenAddListenerSucceeds() + fakeVerificationService.givenRemoveListenerSucceeds() + return fakeVerificationService + } +} From 3bba9dea25fb8adada71205bb96098a4b52bfaa4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 16:30:40 +0200 Subject: [PATCH 0365/1068] Adding unit test for filter action --- .../OtherSessionsViewModelTest.kt | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index c1018003a5..41c54f9255 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -80,7 +80,7 @@ class OtherSessionsViewModelTest { fun `given the viewModel has been initialized then viewState is updated with devices list`() { // Given val devices = mockk>() - givenGetDeviceFullInfoListReturns(devices) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( devices = Success(devices), currentFilter = defaultArgs.defaultFilter, @@ -98,8 +98,41 @@ class OtherSessionsViewModelTest { verifyAll { fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) } } - private fun givenGetDeviceFullInfoListReturns(devices: List) { - every { fakeGetDeviceFullInfoListUseCase.execute(any(), any()) } returns flowOf(devices) + @Test + fun `given filter devices action when handling the action then viewState is updated with filter option and devices are filtered`() { + // Given + val filterType = DeviceManagerFilterType.UNVERIFIED + val devices = mockk>() + val filteredDevices = mockk>() + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + givenGetDeviceFullInfoListReturns(filterType = filterType, filteredDevices) + val expectedState = OtherSessionsViewState( + devices = Success(filteredDevices), + currentFilter = filterType, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.FilterDevices(filterType)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + verifyAll { + fakeGetDeviceFullInfoListUseCase.execute(defaultArgs.defaultFilter, defaultArgs.excludeCurrentDevice) + fakeGetDeviceFullInfoListUseCase.execute(filterType, defaultArgs.excludeCurrentDevice) + } + } + + private fun givenGetDeviceFullInfoListReturns( + filterType: DeviceManagerFilterType, + devices: List, + ) { + every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } private fun givenVerificationService(): FakeVerificationService { From 2e99d45c829965e9790da710df7a1898704393d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 17:22:19 +0200 Subject: [PATCH 0366/1068] Adding unit test about select mode --- .../OtherSessionsViewModelTest.kt | 161 +++++++++++++++++- 1 file changed, 153 insertions(+), 8 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index 41c54f9255..fa61918689 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -22,7 +22,9 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase +import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType +import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeVerificationService import im.vector.app.test.test @@ -37,8 +39,11 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel private const val A_TITLE_RES_ID = 1 +private const val A_DEVICE_ID = "device-id" class OtherSessionsViewModelTest { @@ -71,6 +76,16 @@ class OtherSessionsViewModelTest { givenVerificationService() } + private fun givenVerificationService(): FakeVerificationService { + val fakeVerificationService = fakeActiveSessionHolder + .fakeSession + .fakeCryptoService + .fakeVerificationService + fakeVerificationService.givenAddListenerSucceeds() + fakeVerificationService.givenRemoveListenerSucceeds() + return fakeVerificationService + } + @After fun tearDown() { unmockkAll() @@ -128,6 +143,129 @@ class OtherSessionsViewModelTest { } } + @Test + fun `given enable select mode action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val devices: List = listOf(deviceFullInfo) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = true, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.EnableSelectMode(A_DEVICE_ID)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given disable select mode action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = false), deviceFullInfo2.copy(isSelected = false))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.DisableSelectMode) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given toggle selection for device action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val devices: List = listOf(deviceFullInfo) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(A_DEVICE_ID)) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given select all action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = true), deviceFullInfo2.copy(isSelected = true))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.SelectAll) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + + @Test + fun `given deselect all action when handling the action then viewState is updated with correct info`() { + // Given + val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) + givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) + val expectedState = OtherSessionsViewState( + devices = Success(listOf(deviceFullInfo1.copy(isSelected = false), deviceFullInfo2.copy(isSelected = false))), + currentFilter = defaultArgs.defaultFilter, + excludeCurrentDevice = defaultArgs.excludeCurrentDevice, + isSelectModeEnabled = false, + ) + + // When + val viewModel = createViewModel() + val viewModelTest = viewModel.test() + viewModel.handle(OtherSessionsAction.DeselectAll) + + // Then + viewModelTest + .assertLatestState { state -> state == expectedState } + .finish() + } + private fun givenGetDeviceFullInfoListReturns( filterType: DeviceManagerFilterType, devices: List, @@ -135,13 +273,20 @@ class OtherSessionsViewModelTest { every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } - private fun givenVerificationService(): FakeVerificationService { - val fakeVerificationService = fakeActiveSessionHolder - .fakeSession - .fakeCryptoService - .fakeVerificationService - fakeVerificationService.givenAddListenerSucceeds() - fakeVerificationService.givenRemoveListenerSucceeds() - return fakeVerificationService + private fun givenDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { + return DeviceFullInfo( + deviceInfo = DeviceInfo( + deviceId = deviceId, + ), + cryptoDeviceInfo = null, + roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, + isInactive = true, + isCurrentDevice = true, + deviceExtendedInfo = DeviceExtendedInfo( + deviceType = DeviceType.MOBILE, + ), + matrixClientInfo = null, + isSelected = isSelected, + ) } } From b7f9419bd4ad21254dbdd3859f74d17e9e37e1b4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 19 Oct 2022 17:52:55 +0200 Subject: [PATCH 0367/1068] Fix usage of @+id in xml file --- vector/src/main/res/layout/item_other_session.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml index 908a0e0073..f514cea56b 100644 --- a/vector/src/main/res/layout/item_other_session.xml +++ b/vector/src/main/res/layout/item_other_session.xml @@ -5,8 +5,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?selectableItemBackground" - android:paddingTop="8dp" - android:paddingHorizontal="8dp"> + android:paddingHorizontal="8dp" + android:paddingTop="8dp"> @@ -80,6 +80,6 @@ android:background="?vctr_content_quinary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView" - app:layout_constraintTop_toBottomOf="@+id/otherSessionItemBackground" /> + app:layout_constraintTop_toBottomOf="@id/otherSessionItemBackground" /> From 600f65025661e81e834345fde69983a612b7a244 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 20 Oct 2022 16:48:02 +0200 Subject: [PATCH 0368/1068] Fixing visibility of the select session action when empty list --- .../settings/devices/v2/othersessions/OtherSessionsFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 958266631e..d166a38d54 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -46,6 +46,7 @@ import im.vector.app.features.settings.devices.v2.list.OtherSessionsView import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import im.vector.app.features.themes.ThemeUtils +import org.matrix.android.sdk.api.extensions.orFalse import javax.inject.Inject @AndroidEntryPoint @@ -75,7 +76,7 @@ class OtherSessionsFragment : val isSelectModeEnabled = state.isSelectModeEnabled menu.findItem(R.id.otherSessionsSelectAll).isVisible = isSelectModeEnabled menu.findItem(R.id.otherSessionsDeselectAll).isVisible = isSelectModeEnabled - menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled + menu.findItem(R.id.otherSessionsSelect).isVisible = !isSelectModeEnabled && state.devices()?.isNotEmpty().orFalse() } } From 3e1c110343096686dfe35ad02613f763f2a5dc45 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:21:55 +0200 Subject: [PATCH 0369/1068] Updating some new string keys to make them more generic --- .../ui-strings/src/main/res/values/strings.xml | 15 +++++++++------ .../v2/othersessions/OtherSessionsFragment.kt | 2 +- vector/src/main/res/menu/menu_other_sessions.xml | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7c74162859..897c2853d8 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1,6 +1,13 @@ + + + %1$d selected + %1$d selected + + + %s\'s invitation Your invitation %1$s created the room @@ -407,6 +414,8 @@ Learn more Next Got it + Select all + Deselect all Copied to clipboard @@ -3329,12 +3338,6 @@ No inactive sessions found. Clear Filter Select sessions - Select all - Deselect all - - %1$d selected - %1$d selected - Sign out of this session Session details Application, device, and activity information. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index d166a38d54..4f1c8353f5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -173,7 +173,7 @@ class OtherSessionsFragment : invalidateOptionsMenu() val title = if (isSelectModeEnabled) { val selection = devices.count { it.isSelected } - stringProvider.getQuantityString(R.plurals.device_manager_other_sessions_selected, selection, selection) + stringProvider.getQuantityString(R.plurals.x_selected, selection, selection) } else { getString(args.titleResourceId) } diff --git a/vector/src/main/res/menu/menu_other_sessions.xml b/vector/src/main/res/menu/menu_other_sessions.xml index 0edab99127..8339286fe7 100644 --- a/vector/src/main/res/menu/menu_other_sessions.xml +++ b/vector/src/main/res/menu/menu_other_sessions.xml @@ -11,11 +11,11 @@ From db17d02f36ad02b759d728827e801ea4a15702de Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:25:12 +0200 Subject: [PATCH 0370/1068] Using host variable to make the code nicer --- .../settings/devices/v2/list/OtherSessionsController.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index 9193479b74..8d70552101 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -72,9 +72,9 @@ class OtherSessionsController @Inject constructor( sessionDescription(description) sessionDescriptionDrawable(descriptionDrawable) sessionDescriptionColor(descriptionColor) - stringProvider(this@OtherSessionsController.stringProvider) - colorProvider(this@OtherSessionsController.colorProvider) - drawableProvider(this@OtherSessionsController.drawableProvider) + stringProvider(host.stringProvider) + colorProvider(host.colorProvider) + drawableProvider(host.drawableProvider) selected(device.isSelected) clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } } onLongClickListener(View.OnLongClickListener { From e765575cf6cde04c426723503c710af0c8940a64 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 25 Oct 2022 17:31:46 +0200 Subject: [PATCH 0371/1068] Renaming and creating a fixture method for DeviceFullInfo mocks --- .../OtherSessionsViewModelTest.kt | 38 +++++------------- .../test/fixtures/DeviceFullInfoFixture.kt | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 29 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index fa61918689..e7b8eeee9b 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -22,11 +22,10 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase -import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType -import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeVerificationService +import im.vector.app.test.fixtures.aDeviceFullInfo import im.vector.app.test.test import im.vector.app.test.testDispatcher import io.mockk.every @@ -39,8 +38,6 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test -import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo -import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel private const val A_TITLE_RES_ID = 1 private const val A_DEVICE_ID = "device-id" @@ -146,7 +143,7 @@ class OtherSessionsViewModelTest { @Test fun `given enable select mode action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) val devices: List = listOf(deviceFullInfo) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -170,8 +167,8 @@ class OtherSessionsViewModelTest { @Test fun `given disable select mode action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -195,7 +192,7 @@ class OtherSessionsViewModelTest { @Test fun `given toggle selection for device action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) val devices: List = listOf(deviceFullInfo) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -219,8 +216,8 @@ class OtherSessionsViewModelTest { @Test fun `given select all action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -244,8 +241,8 @@ class OtherSessionsViewModelTest { @Test fun `given deselect all action when handling the action then viewState is updated with correct info`() { // Given - val deviceFullInfo1 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = false) - val deviceFullInfo2 = givenDeviceFullInfo(A_DEVICE_ID, isSelected = true) + val deviceFullInfo1 = aDeviceFullInfo(A_DEVICE_ID, isSelected = false) + val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) val expectedState = OtherSessionsViewState( @@ -272,21 +269,4 @@ class OtherSessionsViewModelTest { ) { every { fakeGetDeviceFullInfoListUseCase.execute(filterType, any()) } returns flowOf(devices) } - - private fun givenDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { - return DeviceFullInfo( - deviceInfo = DeviceInfo( - deviceId = deviceId, - ), - cryptoDeviceInfo = null, - roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, - isInactive = true, - isCurrentDevice = true, - deviceExtendedInfo = DeviceExtendedInfo( - deviceType = DeviceType.MOBILE, - ), - matrixClientInfo = null, - isSelected = isSelected, - ) - } } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt new file mode 100644 index 0000000000..d5f987b5c6 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/DeviceFullInfoFixture.kt @@ -0,0 +1,40 @@ +/* + * 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.test.fixtures + +import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo +import im.vector.app.features.settings.devices.v2.list.DeviceType +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel + +fun aDeviceFullInfo(deviceId: String, isSelected: Boolean): DeviceFullInfo { + return DeviceFullInfo( + deviceInfo = DeviceInfo( + deviceId = deviceId, + ), + cryptoDeviceInfo = null, + roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted, + isInactive = true, + isCurrentDevice = true, + deviceExtendedInfo = DeviceExtendedInfo( + deviceType = DeviceType.MOBILE, + ), + matrixClientInfo = null, + isSelected = isSelected, + ) +} From 3632e6dc8adb5cb7c3b63cf96a5b8e7217e153a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Oct 2022 12:32:27 +0200 Subject: [PATCH 0372/1068] Replace library `org.apache.sanselan:sanselan:0.97-incubator` with `org.apache.commons:commons-imaging:1.0-alpha3` --- dependencies.gradle | 2 +- dependencies_groups.gradle | 1 - .../session/content/ImageExifTagRemover.kt | 35 +++++++++---------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index db4a961648..33a2096a43 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -160,7 +160,7 @@ ext.libs = [ 'emojiGoogle' : "com.vanniktech:emoji-google:$vanniktechEmoji" ], apache : [ - 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" + 'commonsImaging' : "org.apache.commons:commons-imaging:1.0-alpha3" ], sentry: [ 'sentryAndroid' : "io.sentry:sentry-android:$sentry" diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 68de2c1581..8d488ba2f8 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -176,7 +176,6 @@ ext.groups = [ 'org.apache.ant', 'org.apache.commons', 'org.apache.httpcomponents', - 'org.apache.sanselan', 'org.bouncycastle', 'org.ccil.cowan.tagsoup', 'org.checkerframework', diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt index 3fa9ffb0e1..baa441a74d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.internal.session.content import kotlinx.coroutines.withContext -import org.apache.sanselan.Sanselan -import org.apache.sanselan.formats.jpeg.JpegImageMetadata -import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter -import org.apache.sanselan.formats.tiff.constants.ExifTagConstants -import org.apache.sanselan.formats.tiff.constants.GPSTagConstants +import org.apache.commons.imaging.Imaging +import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata +import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter +import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants +import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.util.TemporaryFileCreator @@ -46,24 +46,23 @@ internal class ImageExifTagRemover @Inject constructor( */ suspend fun removeSensitiveJpegExifTags(jpegImageFile: File): File = withContext(coroutineDispatchers.io) { val outputSet = tryOrNull("Unable to read JpegImageMetadata") { - (Sanselan.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet + (Imaging.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet } ?: return@withContext jpegImageFile tryOrNull("Unable to remove ExifData") { outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) - outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_1) - outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_2) + outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION) outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LATITUDE) + outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) } ?: return@withContext jpegImageFile val scrubbedFile = temporaryFileCreator.create() From e8046da0ba6ca8891f94c607e82aa12f3fe0a06b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Oct 2022 12:34:03 +0200 Subject: [PATCH 0373/1068] Use `GpsTagConstants.ALL_GPS_TAGS` to remove all tags related to GPS. --- .../internal/session/content/ImageExifTagRemover.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt index baa441a74d..274a2a85e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -53,16 +53,9 @@ internal class ImageExifTagRemover @Inject constructor( outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION) outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LATITUDE) - outputSet.removeField(GpsTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) + GpsTagConstants.ALL_GPS_TAGS.forEach { tagInfo -> + outputSet.removeField(tagInfo) + } } ?: return@withContext jpegImageFile val scrubbedFile = temporaryFileCreator.create() From c8d08e21a819d24a60ff949b5bda2130441ffe08 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Oct 2022 12:36:58 +0200 Subject: [PATCH 0374/1068] Small refactor --- .../internal/session/content/ImageExifTagRemover.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt index 274a2a85e3..1531d70083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -50,10 +50,7 @@ internal class ImageExifTagRemover @Inject constructor( } ?: return@withContext jpegImageFile tryOrNull("Unable to remove ExifData") { - outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) - outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION) - outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) - GpsTagConstants.ALL_GPS_TAGS.forEach { tagInfo -> + tagsToRemove.forEach { tagInfo -> outputSet.removeField(tagInfo) } } ?: return@withContext jpegImageFile @@ -74,4 +71,12 @@ internal class ImageExifTagRemover @Inject constructor( } ) } + + private val tagsToRemove + get() = GpsTagConstants.ALL_GPS_TAGS + + listOf( + ExifTagConstants.EXIF_TAG_GPSINFO, + ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION, + ExifTagConstants.EXIF_TAG_USER_COMMENT, + ) } From 307b71dbd79f7f15849ed90a19cc52d10ec3b0d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Oct 2022 12:40:45 +0200 Subject: [PATCH 0375/1068] Changelog --- changelog.d/7454.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7454.misc diff --git a/changelog.d/7454.misc b/changelog.d/7454.misc new file mode 100644 index 0000000000..beba9717b8 --- /dev/null +++ b/changelog.d/7454.misc @@ -0,0 +1 @@ +Replace org.apache.sanselan:sanselan by org.apache.commons:commons-imaging From 2f14d191302b581a37fc6cd3cc5846d1009530b9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 11:26:12 +0200 Subject: [PATCH 0376/1068] Fix failing test --- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 9fa6b7a450..f95ab2053b 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -49,10 +49,11 @@ class StartVoiceBroadcastUseCaseTest { private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( - fakeSession, - fakeVoiceBroadcastRecorder, - FakeContext().instance, - mockk() + session = fakeSession, + voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, + context = FakeContext().instance, + buildMeta = mockk(), + getOngoingVoiceBroadcastsUseCase = GetOngoingVoiceBroadcastsUseCase(fakeSession), ) @Test From 5855fe1242d7e99317b79435a541bf2bd68980ef Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:44:18 +0200 Subject: [PATCH 0377/1068] Add StopOngoingVoiceBroadcastUseCase --- .../features/home/HomeActivityViewModel.kt | 35 +---------- .../StopOngoingVoiceBroadcastUseCase.kt | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 32 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 2c45709291..c3abdde022 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -42,8 +42,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences -import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper -import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.StopOngoingVoiceBroadcastUseCase import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -62,14 +61,12 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.room.model.Membership @@ -96,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val analyticsConfig: AnalyticsConfig, private val releaseNotesPreferencesStore: ReleaseNotesPreferencesStore, private val vectorFeatures: VectorFeatures, - private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val stopOngoingVoiceBroadcastUseCase: StopOngoingVoiceBroadcastUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -128,7 +125,7 @@ class HomeActivityViewModel @AssistedInject constructor( observeReleaseNotes() observeLocalNotificationsSilenced() initThreadsMigration() - stopOngoingVoiceBroadcast() + viewModelScope.launch { stopOngoingVoiceBroadcastUseCase.execute() } } private fun observeReleaseNotes() = withState { state -> @@ -496,32 +493,6 @@ class HomeActivityViewModel @AssistedInject constructor( } } - /** - * Stop ongoing voice broadcast if any. - */ - private fun stopOngoingVoiceBroadcast() { - val session = activeSessionHolder.getSafeActiveSession() ?: return - - // FIXME Iterate only on recent rooms for the moment, improve this - val recentRooms = session.roomService() - .getBreadcrumbs(roomSummaryQueryParams { - displayName = QueryStringValue.NoCondition - memberships = listOf(Membership.JOIN) - }) - .mapNotNull { session.getRoom(it.roomId) } - - recentRooms - .forEach { room -> - val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) - val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId - val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } - if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { - viewModelScope.launch { voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } - return // No need to iterate more as we should not have more than one recording VB - } - } - } - override fun handle(action: HomeActivityViewActions) { when (action) { HomeActivityViewActions.PushPromptHasBeenReviewed -> { diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..82baa5e6a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt @@ -0,0 +1,63 @@ +/* + * 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.voicebroadcast.usecase + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import timber.log.Timber +import javax.inject.Inject + +/** + * Stop ongoing voice broadcast if any. + */ +class StopOngoingVoiceBroadcastUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, + private val voiceBroadcastHelper: VoiceBroadcastHelper, +) { + + suspend fun execute() { + Timber.d("## StopOngoingVoiceBroadcastUseCase: Stop ongoing voice broadcast requested") + + val session = activeSessionHolder.getSafeActiveSession() ?: run { + Timber.w("## StopOngoingVoiceBroadcastUseCase: no active session") + return + } + // FIXME Iterate only on recent rooms for the moment, improve this + val recentRooms = session.roomService() + .getBreadcrumbs(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + .mapNotNull { session.getRoom(it.roomId) } + + recentRooms + .forEach { room -> + val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId + val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } + if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { + voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) + return // No need to iterate more as we should not have more than one recording VB + } + } + } +} From 443d573205bec14e5a7f12d0220da0a659194f4c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:48:32 +0200 Subject: [PATCH 0378/1068] Remove getOngoingVoiceBroadcasts from VoiceBroadcastHelper --- .../app/features/voicebroadcast/VoiceBroadcastHelper.kt | 4 ---- .../usecase/StopOngoingVoiceBroadcastUseCase.kt | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index ee9034661c..58e7de7f32 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -16,7 +16,6 @@ package im.vector.app.features.voicebroadcast -import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase @@ -31,7 +30,6 @@ class VoiceBroadcastHelper @Inject constructor( private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, - private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastPlayer: VoiceBroadcastPlayer, ) { suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) @@ -47,6 +45,4 @@ class VoiceBroadcastHelper @Inject constructor( fun pausePlayback() = voiceBroadcastPlayer.pause() fun stopPlayback() = voiceBroadcastPlayer.stop() - - fun getOngoingVoiceBroadcasts(roomId: String) = getOngoingVoiceBroadcastsUseCase.execute(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt index 82baa5e6a8..ab4d16ab60 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopOngoingVoiceBroadcastUseCase.kt @@ -31,6 +31,7 @@ import javax.inject.Inject */ class StopOngoingVoiceBroadcastUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val voiceBroadcastHelper: VoiceBroadcastHelper, ) { @@ -51,7 +52,7 @@ class StopOngoingVoiceBroadcastUseCase @Inject constructor( recentRooms .forEach { room -> - val ongoingVoiceBroadcasts = voiceBroadcastHelper.getOngoingVoiceBroadcasts(room.roomId) + val ongoingVoiceBroadcasts = getOngoingVoiceBroadcastsUseCase.execute(room.roomId) val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { From 23b4f6d42f467d9257f8aae068941ab566af6afb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 12:49:51 +0200 Subject: [PATCH 0379/1068] Inject ActiveSessionHolder in GetOngoingVoiceBroadcastsUseCase --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index db2c625161..47a9ed7b4a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -16,21 +16,22 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject class GetOngoingVoiceBroadcastsUseCase @Inject constructor( - private val session: Session, + private val activeSessionHolder: ActiveSessionHolder, ) { fun execute(roomId: String): List { + val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList() val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") From 0cc2a477b473aeaec0183add3dd3243fc2c49a18 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 14:54:55 +0200 Subject: [PATCH 0380/1068] Mockk GetOngoingVoiceBroadcastsUseCase and adapt tests --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 8 ++++++-- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 16 ++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index 47a9ed7b4a..cb228ad8aa 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -31,8 +31,12 @@ class GetOngoingVoiceBroadcastsUseCase @Inject constructor( ) { fun execute(roomId: String): List { - val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList() - val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + println("## GetOngoingVoiceBroadcastsUseCase") + println("## GetOngoingVoiceBroadcastsUseCase activeSessionHolder $activeSessionHolder") + val session = activeSessionHolder.getSafeActiveSession() + println("## GetOngoingVoiceBroadcastsUseCase session $session") + val room = session?.getRoom(roomId) ?: error("Unknown roomId: $roomId") + println("## GetOngoingVoiceBroadcastsUseCase room $room") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index f95ab2053b..217a395076 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -20,6 +20,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeRoom import im.vector.app.test.fakes.FakeRoomService @@ -27,13 +28,13 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeNull import org.junit.Test -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent @@ -48,12 +49,13 @@ class StartVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val fakeGetOngoingVoiceBroadcastsUseCase = mockk() private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( session = fakeSession, voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, context = FakeContext().instance, buildMeta = mockk(), - getOngoingVoiceBroadcastsUseCase = GetOngoingVoiceBroadcastsUseCase(fakeSession), + getOngoingVoiceBroadcastsUseCase = fakeGetOngoingVoiceBroadcastsUseCase, ) @Test @@ -81,7 +83,7 @@ class StartVoiceBroadcastUseCaseTest { private suspend fun testVoiceBroadcastStarted(voiceBroadcasts: List) { // Given clearAllMocks() - givenAVoiceBroadcasts(voiceBroadcasts) + givenVoiceBroadcasts(voiceBroadcasts) val voiceBroadcastInfoContentInterceptor = slot() coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } @@ -104,7 +106,7 @@ class StartVoiceBroadcastUseCaseTest { private suspend fun testVoiceBroadcastNotStarted(voiceBroadcasts: List) { // Given clearAllMocks() - givenAVoiceBroadcasts(voiceBroadcasts) + givenVoiceBroadcasts(voiceBroadcasts) // When startVoiceBroadcastUseCase.execute(A_ROOM_ID) @@ -113,7 +115,7 @@ class StartVoiceBroadcastUseCaseTest { coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } } - private fun givenAVoiceBroadcasts(voiceBroadcasts: List) { + private fun givenVoiceBroadcasts(voiceBroadcasts: List) { val events = voiceBroadcasts.map { Event( type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, @@ -123,7 +125,9 @@ class StartVoiceBroadcastUseCaseTest { ).toContent() ) } - fakeRoom.stateService().givenGetStateEvents(QueryStringValue.IsNotEmpty, events) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + every { fakeGetOngoingVoiceBroadcastsUseCase.execute(any()) } returns events } private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState) From d242ab049b0c5c09ab1ea0f8b3dda2aa805472a0 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 26 Oct 2022 15:15:48 +0200 Subject: [PATCH 0381/1068] [Rich text editor] Implement full screen editor mode (simple approach) (#7436) * Rich text editor: implement full screen editor mode using ConstraintSets * Add back press handler * Change ToggleFullScreen to SetFullScreen, fix rebase issues * Add warning to fragment_timeline* files --- changelog.d/7436.feature | 1 + .../src/main/res/values/strings.xml | 1 + vector/src/main/AndroidManifest.xml | 3 +- .../app/core/extensions/ViewExtensions.kt | 21 ++ .../JumpToBottomViewVisibilityManager.kt | 10 +- .../home/room/detail/TimelineFragment.kt | 52 +++- .../detail/composer/MessageComposerAction.kt | 2 + .../composer/MessageComposerFragment.kt | 22 +- .../detail/composer/MessageComposerView.kt | 12 +- .../composer/MessageComposerViewModel.kt | 15 +- .../composer/MessageComposerViewState.kt | 1 + .../composer/PlainTextComposerLayout.kt | 12 +- .../detail/composer/RichTextComposerLayout.kt | 47 ++-- .../res/drawable/ic_composer_full_screen.xml | 9 + .../res/layout/composer_rich_text_layout.xml | 14 +- ...ich_text_layout_constraint_set_compact.xml | 21 +- ...ch_text_layout_constraint_set_expanded.xml | 18 +- ..._text_layout_constraint_set_fullscreen.xml | 217 +++++++++++++++ .../src/main/res/layout/fragment_composer.xml | 4 +- .../src/main/res/layout/fragment_timeline.xml | 18 ++ .../layout/fragment_timeline_fullscreen.xml | 258 ++++++++++++++++++ 21 files changed, 705 insertions(+), 53 deletions(-) create mode 100644 changelog.d/7436.feature create mode 100644 vector/src/main/res/drawable/ic_composer_full_screen.xml create mode 100644 vector/src/main/res/layout/composer_rich_text_layout_constraint_set_fullscreen.xml create mode 100644 vector/src/main/res/layout/fragment_timeline_fullscreen.xml diff --git a/changelog.d/7436.feature b/changelog.d/7436.feature new file mode 100644 index 0000000000..b038c975e1 --- /dev/null +++ b/changelog.d/7436.feature @@ -0,0 +1 @@ +Rich text editor: add full screen mode. diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index ea9b4b5999..450dcab1f7 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3423,5 +3423,6 @@ Apply italic format Apply strikethrough format Apply underline format + Toggle full screen mode diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index b0cd202d12..11a54e9f82 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -150,7 +150,8 @@ + android:parentActivityName=".features.home.HomeActivity" + android:windowSoftInputMode="adjustResize"> diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt index 625ff15ef7..156809d5ad 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt @@ -29,7 +29,13 @@ import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.isVisible +import androidx.transition.ChangeBounds +import androidx.transition.Fade +import androidx.transition.Transition +import androidx.transition.TransitionManager +import androidx.transition.TransitionSet import im.vector.app.R +import im.vector.app.core.animations.SimpleTransitionListener import im.vector.app.features.themes.ThemeUtils /** @@ -90,3 +96,18 @@ fun View.setAttributeBackground(@AttrRes attributeId: Int) { val attribute = ThemeUtils.getAttribute(context, attributeId)!! setBackgroundResource(attribute.resourceId) } + +fun ViewGroup.animateLayoutChange(animationDuration: Long, transitionComplete: (() -> Unit)? = null) { + val transition = TransitionSet().apply { + ordering = TransitionSet.ORDERING_SEQUENTIAL + addTransition(ChangeBounds()) + addTransition(Fade(Fade.IN)) + duration = animationDuration + addListener(object : SimpleTransitionListener() { + override fun onTransitionEnd(transition: Transition) { + transitionComplete?.invoke() + } + }) + } + TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt index 0f7dc251ae..1368b71ec6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt @@ -34,6 +34,8 @@ class JumpToBottomViewVisibilityManager( private val layoutManager: LinearLayoutManager ) { + private var canShowButtonOnScroll = true + init { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { @@ -43,7 +45,7 @@ class JumpToBottomViewVisibilityManager( if (scrollingToPast) { jumpToBottomView.hide() - } else { + } else if (canShowButtonOnScroll) { maybeShowJumpToBottomViewVisibility() } } @@ -66,7 +68,13 @@ class JumpToBottomViewVisibilityManager( } } + fun hideAndPreventVisibilityChangesWithScrolling() { + jumpToBottomView.hide() + canShowButtonOnScroll = false + } + private fun maybeShowJumpToBottomViewVisibility() { + canShowButtonOnScroll = true if (layoutManager.findFirstVisibleItemPosition() > 1) { jumpToBottomView.show() } else { 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 9d50cdb070..4f51922a62 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 @@ -32,7 +32,9 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.activity.addCallback import androidx.appcompat.view.menu.MenuBuilder +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.net.toUri @@ -64,6 +66,7 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.epoxy.LayoutManagerStateRestorer +import im.vector.app.core.extensions.animateLayoutChange import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.containsRtLOverride @@ -183,7 +186,9 @@ import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -337,6 +342,7 @@ class TimelineFragment : setupJumpToBottomView() setupRemoveJitsiWidgetView() setupLiveLocationIndicator() + setupBackPressHandling() views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) @@ -414,6 +420,31 @@ class TimelineFragment : if (savedInstanceState == null) { handleSpaceShare() } + + views.scrim.setOnClickListener { + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) + } + + messageComposerViewModel.stateFlow.map { it.isFullScreen } + .distinctUntilChanged() + .onEach { isFullScreen -> + toggleFullScreenEditor(isFullScreen) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + + private fun setupBackPressHandling() { + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + withState(messageComposerViewModel) { state -> + if (state.isFullScreen) { + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) + } else { + remove() // Remove callback to avoid infinite loop + @Suppress("DEPRECATION") + requireActivity().onBackPressed() + } + } + } } private fun setupRemoveJitsiWidgetView() { @@ -1016,7 +1047,13 @@ class TimelineFragment : override fun onLayoutCompleted(state: RecyclerView.State) { super.onLayoutCompleted(state) updateJumpToReadMarkerViewVisibility() - jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() + withState(messageComposerViewModel) { composerState -> + if (!composerState.isFullScreen) { + jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() + } else { + jumpToBottomViewVisibilityManager.hideAndPreventVisibilityChangesWithScrolling() + } + } } }.apply { // For local rooms, pin the view's content to the top edge (the layout is reversed) @@ -2002,6 +2039,19 @@ class TimelineFragment : } } + private fun toggleFullScreenEditor(isFullScreen: Boolean) { + views.composerContainer.animateLayoutChange(200) + + val constraintSet = ConstraintSet() + val constraintSetId = if (isFullScreen) { + R.layout.fragment_timeline_fullscreen + } else { + R.layout.fragment_timeline + } + constraintSet.clone(requireContext(), constraintSetId) + constraintSet.applyTo(views.rootConstraintLayout) + } + /** * Returns true if the current room is a Thread room, false otherwise. */ 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 82adcd014a..30437a016d 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 @@ -34,6 +34,8 @@ sealed class MessageComposerAction : VectorViewModelAction { data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction() data class InsertUserDisplayName(val userId: String) : MessageComposerAction() + data class SetFullScreen(val isFullScreen: Boolean) : MessageComposerAction() + // Voice Message data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 55ec922a57..beb7215c22 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -92,6 +92,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData import im.vector.app.features.voice.VoiceFailure import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -219,6 +220,13 @@ class MessageComposerFragment : VectorBaseFragment(), A } } + messageComposerViewModel.stateFlow.map { it.isFullScreen } + .distinctUntilChanged() + .onEach { isFullScreen -> + composer.toggleFullScreen(isFullScreen) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + if (savedInstanceState != null) { handleShareData() } @@ -297,7 +305,7 @@ class MessageComposerFragment : VectorBaseFragment(), A // Show keyboard when the user started a thread composerEditText.showKeyboard(andRequestFocus = true) } - composer.callback = object : PlainTextComposerLayout.Callback { + composer.callback = object : Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) @@ -320,8 +328,12 @@ class MessageComposerFragment : VectorBaseFragment(), A composer.emojiButton?.isVisible = isEmojiKeyboardVisible } - override fun onSendMessage(text: CharSequence) { + override fun onSendMessage(text: CharSequence) = withState(messageComposerViewModel) { state -> sendTextMessage(text, composer.formattedText) + + if (state.isFullScreen) { + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(false)) + } } override fun onCloseRelatedMessage() { @@ -335,6 +347,10 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onTextChanged(text: CharSequence) { messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) } + + override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state -> + messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen)) + } } } @@ -461,7 +477,7 @@ class MessageComposerFragment : VectorBaseFragment(), A composer.sendButton.alpha = 0f composer.sendButton.isVisible = true composer.sendButton.animate().alpha(1f).setDuration(150).start() - } else { + } else if (!event.isVisible) { composer.sendButton.isInvisible = true } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index 09357191b4..b7e0e29679 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -30,13 +30,14 @@ interface MessageComposerView { val emojiButton: ImageButton? val sendButton: ImageButton val attachmentButton: ImageButton + val fullScreenButton: ImageButton? val composerRelatedMessageTitle: TextView val composerRelatedMessageContent: TextView val composerRelatedMessageImage: ImageView val composerRelatedMessageActionIcon: ImageView val composerRelatedMessageAvatar: ImageView - var callback: PlainTextComposerLayout.Callback? + var callback: Callback? var isVisible: Boolean @@ -44,6 +45,15 @@ interface MessageComposerView { fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) fun setTextIfDifferent(text: CharSequence?): Boolean fun replaceFormattedContent(text: CharSequence) + fun toggleFullScreen(newValue: Boolean) fun setInvisible(isInvisible: Boolean) } + +interface Callback : ComposerEditText.Callback { + fun onCloseRelatedMessage() + fun onSendMessage(text: CharSequence) + fun onAddAttachment() + fun onExpandOrCompactChange() + fun onFullScreenModeChanged() +} 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 1a9f9e6291..23d6e71114 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 @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.composer +import android.text.SpannableString import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -122,6 +123,7 @@ class MessageComposerViewModel @AssistedInject constructor( is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action) is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action) is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action) + is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action) } } @@ -130,12 +132,11 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) { - setState { - // Makes sure currentComposerText is upToDate when accessing further setState - currentComposerText = action.text - this + val needsSendButtonVisibilityUpdate = currentComposerText.isEmpty() != action.text.isEmpty() + currentComposerText = SpannableString(action.text) + if (needsSendButtonVisibilityUpdate) { + updateIsSendButtonVisibility(true) } - updateIsSendButtonVisibility(true) } private fun subscribeToStateInternal() { @@ -163,6 +164,10 @@ class MessageComposerViewModel @AssistedInject constructor( } } + private fun handleSetFullScreen(action: MessageComposerAction.SetFullScreen) { + setState { copy(isFullScreen = action.isFullScreen) } + } + private fun observePowerLevelAndEncryption() { combine( PowerLevelsFlowFactory(room).createFlow(), 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 0df1dbebd8..7bb9509599 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 @@ -70,6 +70,7 @@ data class MessageComposerViewState( val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle, val voiceBroadcastState: VoiceBroadcastState? = null, val text: CharSequence? = null, + val isFullScreen: Boolean = false, ) : MavericksState { val isVoiceRecording = when (voiceRecordingUiState) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt index acb5a1b42a..939a59fcca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -49,13 +49,6 @@ class PlainTextComposerLayout @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView { - interface Callback : ComposerEditText.Callback { - fun onCloseRelatedMessage() - fun onSendMessage(text: CharSequence) - fun onAddAttachment() - fun onExpandOrCompactChange() - } - private val views: ComposerLayoutBinding override var callback: Callback? = null @@ -83,6 +76,7 @@ class PlainTextComposerLayout @JvmOverloads constructor( } override val attachmentButton: ImageButton get() = views.attachmentButton + override val fullScreenButton: ImageButton? = null override val composerRelatedMessageActionIcon: ImageView get() = views.composerRelatedMessageActionIcon override val composerRelatedMessageAvatar: ImageView @@ -155,6 +149,10 @@ class PlainTextComposerLayout @JvmOverloads constructor( return views.composerEditText.setTextIfDifferent(text) } + override fun toggleFullScreen(newValue: Boolean) { + // Plain text composer has no full screen + } + private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { // val wasSendButtonInvisible = views.sendButton.isInvisible if (animate) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 07b7d151ad..cac8f8bed4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -21,7 +21,6 @@ import android.text.Editable import android.text.TextWatcher import android.util.AttributeSet import android.view.LayoutInflater -import android.view.ViewGroup import android.widget.EditText import android.widget.ImageButton import android.widget.ImageView @@ -33,13 +32,8 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.text.toSpannable import androidx.core.view.isInvisible import androidx.core.view.isVisible -import androidx.transition.ChangeBounds -import androidx.transition.Fade -import androidx.transition.Transition -import androidx.transition.TransitionManager -import androidx.transition.TransitionSet import im.vector.app.R -import im.vector.app.core.animations.SimpleTransitionListener +import im.vector.app.core.extensions.animateLayoutChange import im.vector.app.core.extensions.setTextIfDifferent import im.vector.app.databinding.ComposerRichTextLayoutBinding import im.vector.app.databinding.ViewRichTextMenuButtonBinding @@ -56,12 +50,13 @@ class RichTextComposerLayout @JvmOverloads constructor( private val views: ComposerRichTextLayoutBinding - override var callback: PlainTextComposerLayout.Callback? = null + override var callback: Callback? = null private var currentConstraintSetId: Int = -1 - private val animationDuration = 100L + private var isFullScreen = false + override val text: Editable? get() = views.composerEditText.text override val formattedText: String? @@ -74,6 +69,8 @@ class RichTextComposerLayout @JvmOverloads constructor( get() = views.sendButton override val attachmentButton: ImageButton get() = views.attachmentButton + override val fullScreenButton: ImageButton? + get() = views.composerFullScreenButton override val composerRelatedMessageActionIcon: ImageView get() = views.composerRelatedMessageActionIcon override val composerRelatedMessageAvatar: ImageView @@ -124,6 +121,10 @@ class RichTextComposerLayout @JvmOverloads constructor( callback?.onAddAttachment() } + views.composerFullScreenButton.setOnClickListener { + callback?.onFullScreenModeChanged() + } + setupRichTextMenu() } @@ -205,34 +206,30 @@ class RichTextComposerLayout @JvmOverloads constructor( return views.composerEditText.setTextIfDifferent(text) } + override fun toggleFullScreen(newValue: Boolean) { + val constraintSetId = if (newValue) R.layout.composer_rich_text_layout_constraint_set_fullscreen else currentConstraintSetId + ConstraintSet().also { + it.clone(context, constraintSetId) + it.applyTo(this) + } + + updateTextFieldBorder(newValue) + } + private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { // val wasSendButtonInvisible = views.sendButton.isInvisible if (animate) { - configureAndBeginTransition(transitionComplete) + animateLayoutChange(animationDuration, transitionComplete) } ConstraintSet().also { it.clone(context, currentConstraintSetId) it.applyTo(this) } + // Might be updated by view state just after, but avoid blinks // views.sendButton.isInvisible = wasSendButtonInvisible } - private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) { - val transition = TransitionSet().apply { - ordering = TransitionSet.ORDERING_SEQUENTIAL - addTransition(ChangeBounds()) - addTransition(Fade(Fade.IN)) - duration = animationDuration - addListener(object : SimpleTransitionListener() { - override fun onTransitionEnd(transition: Transition) { - transitionComplete?.invoke() - } - }) - } - TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) - } - override fun setInvisible(isInvisible: Boolean) { this.isInvisible = isInvisible } diff --git a/vector/src/main/res/drawable/ic_composer_full_screen.xml b/vector/src/main/res/drawable/ic_composer_full_screen.xml new file mode 100644 index 0000000000..394dc52279 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_full_screen.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml index 09e4b03887..9f49b8f9d6 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" tools:constraintSet="@layout/composer_rich_text_layout_constraint_set_compact" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> @@ -108,12 +108,24 @@ style="@style/Widget.Vector.EditText.RichTextComposer" android:layout_width="0dp" android:layout_height="wrap_content" + android:gravity="top" android:nextFocusLeft="@id/composerEditText" android:nextFocusUp="@id/composerEditText" tools:hint="@string/room_message_placeholder" tools:text="@tools:sample/lorem/random" tools:ignore="MissingConstraints" /> + + @@ -114,6 +114,7 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/option_send_files" android:src="@drawable/ic_attachment" + app:layout_constraintVertical_bias="1" app:layout_constraintBottom_toBottomOf="@id/sendButton" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/sendButton" @@ -142,14 +143,26 @@ android:hint="@string/room_message_placeholder" android:nextFocusLeft="@id/composerEditText" android:nextFocusUp="@id/composerEditText" - android:layout_marginHorizontal="12dp" + android:layout_marginStart="12dp" android:layout_marginVertical="10dp" app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder" - app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" + app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton" app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder" tools:text="@tools:sample/lorem/random" /> + + @@ -173,6 +187,7 @@ app:layout_constraintStart_toEndOf="@id/attachmentButton" app:layout_constraintEnd_toStartOf="@id/sendButton" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintVertical_bias="1" android:fillViewport="true"> @@ -156,14 +156,26 @@ android:hint="@string/room_message_placeholder" android:nextFocusLeft="@id/composerEditText" android:nextFocusUp="@id/composerEditText" - android:layout_marginHorizontal="12dp" + android:layout_marginStart="12dp" android:layout_marginVertical="10dp" app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder" - app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" + app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton" app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder" app:layout_constraintTop_toTopOf="@id/composerEditTextOuterBorder" tools:text="@tools:sample/lorem/random" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_composer.xml b/vector/src/main/res/layout/fragment_composer.xml index 8703af7471..41c052367a 100644 --- a/vector/src/main/res/layout/fragment_composer.xml +++ b/vector/src/main/res/layout/fragment_composer.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="match_parent"> + + + + @@ -165,6 +182,7 @@ android:layout_margin="16dp" android:contentDescription="@string/a11y_jump_to_bottom" android:src="@drawable/ic_expand_more" + android:visibility="gone" app:backgroundTint="#FFFFFF" app:badgeBackgroundColor="?colorPrimary" app:badgeTextColor="?colorOnPrimary" diff --git a/vector/src/main/res/layout/fragment_timeline_fullscreen.xml b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml new file mode 100644 index 0000000000..373ca74f56 --- /dev/null +++ b/vector/src/main/res/layout/fragment_timeline_fullscreen.xml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c20f6fe3262761b918cbc5c686335884bea6e57f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 16:07:38 +0200 Subject: [PATCH 0382/1068] GetOngoingVoiceBroadcastsUseCase: Remove debug logs --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index cb228ad8aa..0f5e413719 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -31,12 +31,8 @@ class GetOngoingVoiceBroadcastsUseCase @Inject constructor( ) { fun execute(roomId: String): List { - println("## GetOngoingVoiceBroadcastsUseCase") - println("## GetOngoingVoiceBroadcastsUseCase activeSessionHolder $activeSessionHolder") val session = activeSessionHolder.getSafeActiveSession() - println("## GetOngoingVoiceBroadcastsUseCase session $session") val room = session?.getRoom(roomId) ?: error("Unknown roomId: $roomId") - println("## GetOngoingVoiceBroadcastsUseCase room $room") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") From cb5fc75c5d19751dba201b41f52cccde778893d0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 16:08:03 +0200 Subject: [PATCH 0383/1068] GetOngoingVoiceBroadcastsUseCase: Return empty list if there is no session --- .../usecase/GetOngoingVoiceBroadcastsUseCase.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index 0f5e413719..ec50618969 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -31,8 +31,11 @@ class GetOngoingVoiceBroadcastsUseCase @Inject constructor( ) { fun execute(roomId: String): List { - val session = activeSessionHolder.getSafeActiveSession() - val room = session?.getRoom(roomId) ?: error("Unknown roomId: $roomId") + val session = activeSessionHolder.getSafeActiveSession() ?: run { + Timber.d("## GetOngoingVoiceBroadcastsUseCase: no active session") + return emptyList() + } + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId") From bdfc96ff666859b11376e91635c458dc242412ca Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 26 Oct 2022 16:36:02 +0200 Subject: [PATCH 0384/1068] Fix merge conflicts --- .../detail/composer/MessageComposerFragment.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 3cb3eb1a4b..463a8fe440 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -91,8 +91,8 @@ import im.vector.app.features.poll.PollMode import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData import im.vector.app.features.voice.VoiceFailure -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -207,6 +207,13 @@ class MessageComposerFragment : VectorBaseFragment(), A } } + messageComposerViewModel.stateFlow.map { it.isFullScreen } + .distinctUntilChanged() + .onEach { isFullScreen -> + composer.toggleFullScreen(isFullScreen) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend -> if (!canSend.boolean()) { return@onEach @@ -220,13 +227,6 @@ class MessageComposerFragment : VectorBaseFragment(), A } } - messageComposerViewModel.stateFlow.map { it.isFullScreen } - .distinctUntilChanged() - .onEach { isFullScreen -> - composer.toggleFullScreen(isFullScreen) - } - .launchIn(viewLifecycleOwner.lifecycleScope) - if (savedInstanceState != null) { handleShareData() } From c776aae9d06052abe2eb0d799ac33cc77dc94e9f Mon Sep 17 00:00:00 2001 From: jonnyandrew Date: Wed, 26 Oct 2022 17:37:40 +0100 Subject: [PATCH 0385/1068] [Rich text editor] Add plain text mode and new attachment UI (#7459) * Add new attachments selection dialog * Add rounded corners to bottom sheet dialog. Note these are currently only visible in the collapsed state. - [Google issue](https://issuetracker.google.com/issues/144859239) - [Rejected PR](https://github.com/material-components/material-components-android/pull/437) - [Github issue](https://github.com/material-components/material-components-android/issues/1278) * Add changelog entry * Remove redundant call to superclass click listener * Refactor to use view visibility helper * Change redundant sealed class to interface * Remove unused string * Revert "Add rounded corners to bottom sheet dialog." This reverts commit 17c43c91888162d3c7675511ff910c46c3aa32fc. * Remove redundant view group * Remove redundant `this` * Update rich text editor to latest * Update rich text editor version * Allow toggling rich text in the new editor * Persist the text formatting setting * Add changelog entry --- changelog.d/7429.feature | 1 + changelog.d/7452.feature | 1 + dependencies.gradle | 2 +- .../src/main/res/values/strings.xml | 10 ++ .../app/core/di/MavericksViewModelModule.kt | 6 + .../core/ui/views/BottomSheetActionButton.kt | 4 + .../features/attachments/AttachmentType.kt | 37 +++++ .../AttachmentTypeSelectorBottomSheet.kt | 92 ++++++++++++ ...chmentTypeSelectorSharedActionViewModel.kt | 30 ++++ .../attachments/AttachmentTypeSelectorView.kt | 70 +++++---- .../AttachmentTypeSelectorViewModel.kt | 76 ++++++++++ .../features/attachments/AttachmentsHelper.kt | 2 +- .../composer/MessageComposerFragment.kt | 74 +++++---- .../detail/composer/RichTextComposerLayout.kt | 99 ++++++++---- .../features/settings/VectorPreferences.kt | 19 +++ .../main/res/drawable/ic_text_formatting.xml | 13 ++ .../drawable/ic_text_formatting_disabled.xml | 18 +++ .../bottom_sheet_attachment_type_selector.xml | 106 +++++++++++++ .../res/layout/composer_rich_text_layout.xml | 19 ++- ...ich_text_layout_constraint_set_compact.xml | 22 ++- ...ch_text_layout_constraint_set_expanded.xml | 22 ++- ..._text_layout_constraint_set_fullscreen.xml | 23 ++- .../AttachmentTypeSelectorViewModelTest.kt | 142 ++++++++++++++++++ .../app/test/fakes/FakeVectorFeatures.kt | 8 + .../app/test/fakes/FakeVectorPreferences.kt | 3 + 25 files changed, 797 insertions(+), 102 deletions(-) create mode 100644 changelog.d/7429.feature create mode 100644 changelog.d/7452.feature create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt create mode 100644 vector/src/main/res/drawable/ic_text_formatting.xml create mode 100644 vector/src/main/res/drawable/ic_text_formatting_disabled.xml create mode 100644 vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml create mode 100644 vector/src/test/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModelTest.kt diff --git a/changelog.d/7429.feature b/changelog.d/7429.feature new file mode 100644 index 0000000000..9857452eca --- /dev/null +++ b/changelog.d/7429.feature @@ -0,0 +1 @@ +Add new UI for selecting an attachment diff --git a/changelog.d/7452.feature b/changelog.d/7452.feature new file mode 100644 index 0000000000..a811f87c84 --- /dev/null +++ b/changelog.d/7452.feature @@ -0,0 +1 @@ +[Rich text editor] Add plain text mode diff --git a/dependencies.gradle b/dependencies.gradle index f081e0a874..db6e92552a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -101,7 +101,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.2.1" + 'wysiwyg' : "io.element.android:wysiwyg:0.4.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 450dcab1f7..9edd7d836a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3205,6 +3205,16 @@ Share location Start a voice broadcast + Photo library + Stickers + Attachments + Voice broadcast + Polls + Location + Camera + Contact + Text formatting + Show less "%1$d more" diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 97590028d8..2242abb7aa 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -22,6 +22,7 @@ import dagger.hilt.InstallIn import dagger.multibindings.IntoMap import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel +import im.vector.app.features.attachments.AttachmentTypeSelectorViewModel import im.vector.app.features.auth.ReAuthViewModel import im.vector.app.features.call.VectorCallViewModel import im.vector.app.features.call.conference.JitsiCallViewModel @@ -677,4 +678,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(VectorSettingsLabsViewModel::class) fun vectorSettingsLabsViewModelFactory(factory: VectorSettingsLabsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(AttachmentTypeSelectorViewModel::class) + fun attachmentTypeSelectorViewModelFactory(factory: AttachmentTypeSelectorViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt index a3e8b3780c..ca3e6a360a 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt @@ -38,6 +38,10 @@ class BottomSheetActionButton @JvmOverloads constructor( ) : FrameLayout(context, attrs, defStyleAttr) { val views: ViewBottomSheetActionButtonBinding + override fun setOnClickListener(l: OnClickListener?) { + views.bottomSheetActionClickableZone.setOnClickListener(l) + } + var title: String? = null set(value) { field = value diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt new file mode 100644 index 0000000000..f4b97b9f9c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentType.kt @@ -0,0 +1,37 @@ +/* + * 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.attachments + +import im.vector.app.core.utils.PERMISSIONS_EMPTY +import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING +import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT +import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_BROADCAST + +/** + * The all possible types to pick with their required permissions. + */ +enum class AttachmentType(val permissions: List) { + CAMERA(PERMISSIONS_FOR_TAKING_PHOTO), + GALLERY(PERMISSIONS_EMPTY), + FILE(PERMISSIONS_EMPTY), + STICKER(PERMISSIONS_EMPTY), + CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), + POLL(PERMISSIONS_EMPTY), + LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING), + VOICE_BROADCAST(PERMISSIONS_FOR_VOICE_BROADCAST), +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt new file mode 100644 index 0000000000..f8d5d768ef --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorBottomSheet.kt @@ -0,0 +1,92 @@ +/* + * 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.attachments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.viewModels +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetAttachmentTypeSelectorBinding +import im.vector.app.features.home.room.detail.TimelineViewModel + +@AndroidEntryPoint +class AttachmentTypeSelectorBottomSheet : VectorBaseBottomSheetDialogFragment() { + + private val viewModel: AttachmentTypeSelectorViewModel by parentFragmentViewModel() + private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() + private val sharedActionViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels( + ownerProducer = { requireParentFragment() } + ) + + override val showExpanded = true + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetAttachmentTypeSelectorBinding { + return BottomSheetAttachmentTypeSelectorBinding.inflate(inflater, container, false) + } + + override fun invalidate() = withState(viewModel, timelineViewModel) { viewState, timelineState -> + super.invalidate() + views.location.isVisible = viewState.isLocationVisible + views.voiceBroadcast.isVisible = viewState.isVoiceBroadcastVisible + views.poll.isVisible = !timelineState.isThreadTimeline() + views.textFormatting.isChecked = viewState.isTextFormattingEnabled + views.textFormatting.setCompoundDrawablesRelativeWithIntrinsicBounds( + if (viewState.isTextFormattingEnabled) { + R.drawable.ic_text_formatting + } else { + R.drawable.ic_text_formatting_disabled + }, 0, 0, 0 + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + views.gallery.debouncedClicks { onAttachmentSelected(AttachmentType.GALLERY) } + views.stickers.debouncedClicks { onAttachmentSelected(AttachmentType.STICKER) } + views.file.debouncedClicks { onAttachmentSelected(AttachmentType.FILE) } + views.voiceBroadcast.debouncedClicks { onAttachmentSelected(AttachmentType.VOICE_BROADCAST) } + views.poll.debouncedClicks { onAttachmentSelected(AttachmentType.POLL) } + views.location.debouncedClicks { onAttachmentSelected(AttachmentType.LOCATION) } + views.camera.debouncedClicks { onAttachmentSelected(AttachmentType.CAMERA) } + views.contact.debouncedClicks { onAttachmentSelected(AttachmentType.CONTACT) } + views.textFormatting.setOnCheckedChangeListener { _, isChecked -> onTextFormattingToggled(isChecked) } + } + + private fun onAttachmentSelected(attachmentType: AttachmentType) { + val action = AttachmentTypeSelectorSharedAction.SelectAttachmentTypeAction(attachmentType) + sharedActionViewModel.post(action) + dismiss() + } + + private fun onTextFormattingToggled(isEnabled: Boolean) = + viewModel.handle(AttachmentTypeSelectorAction.ToggleTextFormatting(isEnabled)) + + companion object { + fun show(fragmentManager: FragmentManager) { + val bottomSheet = AttachmentTypeSelectorBottomSheet() + bottomSheet.show(fragmentManager, "AttachmentTypeSelectorBottomSheet") + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt new file mode 100644 index 0000000000..e02b10c54b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorSharedActionViewModel.kt @@ -0,0 +1,30 @@ +/* + * 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.attachments + +import im.vector.app.core.platform.VectorSharedAction +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class AttachmentTypeSelectorSharedActionViewModel @Inject constructor() : + VectorSharedActionViewModel() + +sealed interface AttachmentTypeSelectorSharedAction : VectorSharedAction { + data class SelectAttachmentTypeAction( + val attachmentType: AttachmentType + ) : AttachmentTypeSelectorSharedAction +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index 8536b765d4..55805a0728 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -30,17 +30,11 @@ import android.view.animation.TranslateAnimation import android.widget.ImageButton import android.widget.LinearLayout import android.widget.PopupWindow -import androidx.annotation.StringRes import androidx.appcompat.widget.TooltipCompat import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.epoxy.onClick -import im.vector.app.core.utils.PERMISSIONS_EMPTY -import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING -import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT -import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO -import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_BROADCAST import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback import kotlin.math.max @@ -59,7 +53,7 @@ class AttachmentTypeSelectorView( ) : PopupWindow(context) { interface Callback { - fun onTypeSelected(type: Type) + fun onTypeSelected(type: AttachmentType) } private val views: ViewAttachmentTypeSelectorBinding @@ -69,14 +63,14 @@ class AttachmentTypeSelectorView( init { contentView = inflater.inflate(R.layout.view_attachment_type_selector, null, false) views = ViewAttachmentTypeSelectorBinding.bind(contentView) - views.attachmentGalleryButton.configure(Type.GALLERY) - views.attachmentCameraButton.configure(Type.CAMERA) - views.attachmentFileButton.configure(Type.FILE) - views.attachmentStickersButton.configure(Type.STICKER) - views.attachmentContactButton.configure(Type.CONTACT) - views.attachmentPollButton.configure(Type.POLL) - views.attachmentLocationButton.configure(Type.LOCATION) - views.attachmentVoiceBroadcast.configure(Type.VOICE_BROADCAST) + views.attachmentGalleryButton.configure(AttachmentType.GALLERY) + views.attachmentCameraButton.configure(AttachmentType.CAMERA) + views.attachmentFileButton.configure(AttachmentType.FILE) + views.attachmentStickersButton.configure(AttachmentType.STICKER) + views.attachmentContactButton.configure(AttachmentType.CONTACT) + views.attachmentPollButton.configure(AttachmentType.POLL) + views.attachmentLocationButton.configure(AttachmentType.LOCATION) + views.attachmentVoiceBroadcast.configure(AttachmentType.VOICE_BROADCAST) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -127,16 +121,16 @@ class AttachmentTypeSelectorView( } } - fun setAttachmentVisibility(type: Type, isVisible: Boolean) { + fun setAttachmentVisibility(type: AttachmentType, isVisible: Boolean) { when (type) { - Type.CAMERA -> views.attachmentCameraButton - Type.GALLERY -> views.attachmentGalleryButton - Type.FILE -> views.attachmentFileButton - Type.STICKER -> views.attachmentStickersButton - Type.CONTACT -> views.attachmentContactButton - Type.POLL -> views.attachmentPollButton - Type.LOCATION -> views.attachmentLocationButton - Type.VOICE_BROADCAST -> views.attachmentVoiceBroadcast + AttachmentType.CAMERA -> views.attachmentCameraButton + AttachmentType.GALLERY -> views.attachmentGalleryButton + AttachmentType.FILE -> views.attachmentFileButton + AttachmentType.STICKER -> views.attachmentStickersButton + AttachmentType.CONTACT -> views.attachmentContactButton + AttachmentType.POLL -> views.attachmentPollButton + AttachmentType.LOCATION -> views.attachmentLocationButton + AttachmentType.VOICE_BROADCAST -> views.attachmentVoiceBroadcast }.let { it.isVisible = isVisible } @@ -200,13 +194,13 @@ class AttachmentTypeSelectorView( return Pair(x, y) } - private fun ImageButton.configure(type: Type): ImageButton { + private fun ImageButton.configure(type: AttachmentType): ImageButton { this.setOnClickListener(TypeClickListener(type)) - TooltipCompat.setTooltipText(this, context.getString(type.tooltipRes)) + TooltipCompat.setTooltipText(this, context.getString(attachmentTooltipLabels.getValue(type))) return this } - private inner class TypeClickListener(private val type: Type) : View.OnClickListener { + private inner class TypeClickListener(private val type: AttachmentType) : View.OnClickListener { override fun onClick(v: View) { dismiss() @@ -217,14 +211,18 @@ class AttachmentTypeSelectorView( /** * The all possible types to pick with their required permissions and tooltip resource. */ - enum class Type(val permissions: List, @StringRes val tooltipRes: Int) { - CAMERA(PERMISSIONS_FOR_TAKING_PHOTO, R.string.tooltip_attachment_photo), - GALLERY(PERMISSIONS_EMPTY, R.string.tooltip_attachment_gallery), - FILE(PERMISSIONS_EMPTY, R.string.tooltip_attachment_file), - STICKER(PERMISSIONS_EMPTY, R.string.tooltip_attachment_sticker), - CONTACT(PERMISSIONS_FOR_PICKING_CONTACT, R.string.tooltip_attachment_contact), - POLL(PERMISSIONS_EMPTY, R.string.tooltip_attachment_poll), - LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING, R.string.tooltip_attachment_location), - VOICE_BROADCAST(PERMISSIONS_FOR_VOICE_BROADCAST, R.string.tooltip_attachment_voice_broadcast), + private companion object { + private val attachmentTooltipLabels: Map = AttachmentType.values().associateWith { + when (it) { + AttachmentType.CAMERA -> R.string.tooltip_attachment_photo + AttachmentType.GALLERY -> R.string.tooltip_attachment_gallery + AttachmentType.FILE -> R.string.tooltip_attachment_file + AttachmentType.STICKER -> R.string.tooltip_attachment_sticker + AttachmentType.CONTACT -> R.string.tooltip_attachment_contact + AttachmentType.POLL -> R.string.tooltip_attachment_poll + AttachmentType.LOCATION -> R.string.tooltip_attachment_location + AttachmentType.VOICE_BROADCAST -> R.string.tooltip_attachment_voice_broadcast + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt new file mode 100644 index 0000000000..cb74661eba --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorViewModel.kt @@ -0,0 +1,76 @@ +/* + * 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.attachments + +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.features.VectorFeatures +import im.vector.app.features.settings.VectorPreferences + +class AttachmentTypeSelectorViewModel @AssistedInject constructor( + @Assisted initialState: AttachmentTypeSelectorViewState, + private val vectorFeatures: VectorFeatures, + private val vectorPreferences: VectorPreferences, +) : VectorViewModel(initialState) { + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: AttachmentTypeSelectorViewState): AttachmentTypeSelectorViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: AttachmentTypeSelectorAction) = when (action) { + is AttachmentTypeSelectorAction.ToggleTextFormatting -> setTextFormattingEnabled(action.isEnabled) + } + + init { + setState { + copy( + isLocationVisible = vectorFeatures.isLocationSharingEnabled(), + isVoiceBroadcastVisible = vectorFeatures.isVoiceBroadcastEnabled(), + isTextFormattingEnabled = vectorPreferences.isTextFormattingEnabled(), + ) + } + } + + private fun setTextFormattingEnabled(isEnabled: Boolean) { + vectorPreferences.setTextFormattingEnabled(isEnabled) + setState { + copy( + isTextFormattingEnabled = isEnabled + ) + } + } +} + +data class AttachmentTypeSelectorViewState( + val isLocationVisible: Boolean = false, + val isVoiceBroadcastVisible: Boolean = false, + val isTextFormattingEnabled: Boolean = false, +) : MavericksState + +sealed interface AttachmentTypeSelectorAction : VectorViewModelAction { + data class ToggleTextFormatting(val isEnabled: Boolean) : AttachmentTypeSelectorAction +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt index 1a8e10d102..9692777e15 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt @@ -54,7 +54,7 @@ class AttachmentsHelper( private var captureUri: Uri? = null // The pending type is set if we have to handle permission request. It must be restored if the activity gets killed. - var pendingType: AttachmentTypeSelectorView.Type? = null + var pendingType: AttachmentType? = null // Restorable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 463a8fe440..5666c28605 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -40,8 +40,10 @@ import androidx.core.text.buildSpannedString import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -63,7 +65,12 @@ import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentComposerBinding import im.vector.app.features.VectorFeatures +import im.vector.app.features.attachments.AttachmentType +import im.vector.app.features.attachments.AttachmentTypeSelectorBottomSheet +import im.vector.app.features.attachments.AttachmentTypeSelectorSharedAction +import im.vector.app.features.attachments.AttachmentTypeSelectorSharedActionViewModel import im.vector.app.features.attachments.AttachmentTypeSelectorView +import im.vector.app.features.attachments.AttachmentTypeSelectorViewModel import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.ShareIntentHandler @@ -91,8 +98,9 @@ import im.vector.app.features.poll.PollMode import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.share.SharedData import im.vector.app.features.voice.VoiceFailure -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -162,6 +170,8 @@ class MessageComposerFragment : VectorBaseFragment(), A private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel() private lateinit var sharedActionViewModel: MessageSharedActionViewModel + private val attachmentViewModel: AttachmentTypeSelectorViewModel by fragmentViewModel() + private val attachmentActionsViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels() private val composer: MessageComposerView get() { return if (vectorPreferences.isRichTextEditorEnabled()) { @@ -227,6 +237,11 @@ class MessageComposerFragment : VectorBaseFragment(), A } } + attachmentActionsViewModel.stream() + .filterIsInstance() + .onEach { onTypeSelected(it.attachmentType) } + .launchIn(lifecycleScope) + if (savedInstanceState != null) { handleShareData() } @@ -260,11 +275,14 @@ class MessageComposerFragment : VectorBaseFragment(), A messageComposerViewModel.endAllVoiceActions() } - override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> + override fun invalidate() = withState( + timelineViewModel, messageComposerViewModel, attachmentViewModel + ) { mainState, messageComposerState, attachmentState -> if (mainState.tombstoneEvent != null) return@withState composer.setInvisible(!messageComposerState.isComposerVisible) composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible + (composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled } private fun setupComposer() { @@ -307,21 +325,25 @@ class MessageComposerFragment : VectorBaseFragment(), A } composer.callback = object : Callback { override fun onAddAttachment() { - if (!::attachmentTypeSelector.isInitialized) { - attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.LOCATION, - vectorFeatures.isLocationSharingEnabled(), - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.VOICE_BROADCAST, - vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission - ) + if (vectorPreferences.isRichTextEditorEnabled()) { + AttachmentTypeSelectorBottomSheet.show(childFragmentManager) + } else { + if (!::attachmentTypeSelector.isInitialized) { + attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.LOCATION, + vectorFeatures.isLocationSharingEnabled(), + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.POLL, !isThreadTimeLine() + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentType.VOICE_BROADCAST, + vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission + ) + } + attachmentTypeSelector.show(composer.attachmentButton) } - attachmentTypeSelector.show(composer.attachmentButton) } override fun onExpandOrCompactChange() { @@ -678,20 +700,20 @@ class MessageComposerFragment : VectorBaseFragment(), A } } - private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { + private fun launchAttachmentProcess(type: AttachmentType) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( + AttachmentType.CAMERA -> attachmentsHelper.openCamera( activity = requireActivity(), vectorPreferences = vectorPreferences, cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher ) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) - AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomId, null, PollMode.CREATE) - AttachmentTypeSelectorView.Type.LOCATION -> { + AttachmentType.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentType.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) + AttachmentType.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) + AttachmentType.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentType.POLL -> navigator.openCreatePoll(requireContext(), roomId, null, PollMode.CREATE) + AttachmentType.LOCATION -> { navigator .openLocationSharing( context = requireContext(), @@ -701,11 +723,11 @@ class MessageComposerFragment : VectorBaseFragment(), A locationOwnerId = session.myUserId ) } - AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start) + AttachmentType.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start) } } - override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) { + override fun onTypeSelected(type: AttachmentType) { if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) { launchAttachmentProcess(type) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index cac8f8bed4..2c09f351bb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -38,7 +38,7 @@ import im.vector.app.core.extensions.setTextIfDifferent import im.vector.app.databinding.ComposerRichTextLayoutBinding import im.vector.app.databinding.ViewRichTextMenuButtonBinding import io.element.android.wysiwyg.EditorEditText -import io.element.android.wysiwyg.InlineFormat +import io.element.android.wysiwyg.inputhandlers.models.InlineFormat import uniffi.wysiwyg_composer.ComposerAction import uniffi.wysiwyg_composer.MenuState @@ -57,12 +57,24 @@ class RichTextComposerLayout @JvmOverloads constructor( private var isFullScreen = false + var isTextFormattingEnabled = true + set(value) { + if (field == value) return + syncEditTexts() + field = value + updateEditTextVisibility() + } + override val text: Editable? - get() = views.composerEditText.text + get() = editText.text override val formattedText: String? - get() = views.composerEditText.getHtmlOutput() + get() = (editText as? EditorEditText)?.getHtmlOutput() override val editText: EditText - get() = views.composerEditText + get() = if (isTextFormattingEnabled) { + views.richTextComposerEditText + } else { + views.plainTextComposerEditText + } override val emojiButton: ImageButton? get() = null override val sendButton: ImageButton @@ -91,21 +103,12 @@ class RichTextComposerLayout @JvmOverloads constructor( collapse(false) - views.composerEditText.addTextChangedListener(object : TextWatcher { - private var previousTextWasExpanded = false - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - override fun afterTextChanged(s: Editable) { - callback?.onTextChanged(s) - - val isExpanded = s.lines().count() > 1 - if (previousTextWasExpanded != isExpanded) { - updateTextFieldBorder(isExpanded) - } - previousTextWasExpanded = isExpanded - } - }) + views.richTextComposerEditText.addTextChangedListener( + TextChangeListener({ callback?.onTextChanged(it) }, ::updateTextFieldBorder) + ) + views.plainTextComposerEditText.addTextChangedListener( + TextChangeListener({ callback?.onTextChanged(it) }, ::updateTextFieldBorder) + ) views.composerRelatedMessageCloseButton.setOnClickListener { collapse() @@ -130,19 +133,23 @@ class RichTextComposerLayout @JvmOverloads constructor( private fun setupRichTextMenu() { addRichTextMenuItem(R.drawable.ic_composer_bold, R.string.rich_text_editor_format_bold, ComposerAction.Bold) { - views.composerEditText.toggleInlineFormat(InlineFormat.Bold) + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.Bold) } addRichTextMenuItem(R.drawable.ic_composer_italic, R.string.rich_text_editor_format_italic, ComposerAction.Italic) { - views.composerEditText.toggleInlineFormat(InlineFormat.Italic) + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.Italic) } addRichTextMenuItem(R.drawable.ic_composer_underlined, R.string.rich_text_editor_format_underline, ComposerAction.Underline) { - views.composerEditText.toggleInlineFormat(InlineFormat.Underline) + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.Underline) } addRichTextMenuItem(R.drawable.ic_composer_strikethrough, R.string.rich_text_editor_format_strikethrough, ComposerAction.StrikeThrough) { - views.composerEditText.toggleInlineFormat(InlineFormat.StrikeThrough) + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.StrikeThrough) } + } - views.composerEditText.menuStateChangedListener = EditorEditText.OnMenuStateChangedListener { state -> + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + views.richTextComposerEditText.menuStateChangedListener = EditorEditText.OnMenuStateChangedListener { state -> if (state is MenuState.Update) { updateMenuStateFor(ComposerAction.Bold, state) updateMenuStateFor(ComposerAction.Italic, state) @@ -150,8 +157,26 @@ class RichTextComposerLayout @JvmOverloads constructor( updateMenuStateFor(ComposerAction.StrikeThrough, state) } } + + updateEditTextVisibility() } + private fun updateEditTextVisibility() { + views.richTextComposerEditText.isVisible = isTextFormattingEnabled + views.richTextMenu.isVisible = isTextFormattingEnabled + views.plainTextComposerEditText.isVisible = !isTextFormattingEnabled + } + + /** + * Updates the non-active input with the contents of the active input. + */ + private fun syncEditTexts() = + if (isTextFormattingEnabled) { + views.plainTextComposerEditText.setText(views.richTextComposerEditText.getPlainText()) + } else { + views.richTextComposerEditText.setText(views.plainTextComposerEditText.text.toString()) + } + private fun addRichTextMenuItem(@DrawableRes iconId: Int, @StringRes description: Int, action: ComposerAction, onClick: () -> Unit) { val inflater = LayoutInflater.from(context) val button = ViewRichTextMenuButtonBinding.inflate(inflater, views.richTextMenu, true) @@ -181,7 +206,7 @@ class RichTextComposerLayout @JvmOverloads constructor( } override fun replaceFormattedContent(text: CharSequence) { - views.composerEditText.setHtml(text.toString()) + views.richTextComposerEditText.setHtml(text.toString()) } override fun collapse(animate: Boolean, transitionComplete: (() -> Unit)?) { @@ -191,6 +216,7 @@ class RichTextComposerLayout @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_compact applyNewConstraintSet(animate, transitionComplete) + updateEditTextVisibility() } override fun expand(animate: Boolean, transitionComplete: (() -> Unit)?) { @@ -200,10 +226,11 @@ class RichTextComposerLayout @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_expanded applyNewConstraintSet(animate, transitionComplete) + updateEditTextVisibility() } override fun setTextIfDifferent(text: CharSequence?): Boolean { - return views.composerEditText.setTextIfDifferent(text) + return editText.setTextIfDifferent(text) } override fun toggleFullScreen(newValue: Boolean) { @@ -214,6 +241,7 @@ class RichTextComposerLayout @JvmOverloads constructor( } updateTextFieldBorder(newValue) + updateEditTextVisibility() } private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { @@ -233,4 +261,23 @@ class RichTextComposerLayout @JvmOverloads constructor( override fun setInvisible(isInvisible: Boolean) { this.isInvisible = isInvisible } + + private class TextChangeListener( + private val onTextChanged: (s: Editable) -> Unit, + private val onExpandedChanged: (isExpanded: Boolean) -> Unit, + ) : TextWatcher { + private var previousTextWasExpanded = false + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + onTextChanged.invoke(s) + + val isExpanded = s.lines().count() > 1 + if (previousTextWasExpanded != isExpanded) { + onExpandedChanged(isExpanded) + } + previousTextWasExpanded = isExpanded + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 2dc8b12160..9f40a7cede 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -109,6 +109,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY" private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY" private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY" + private const val SETTINGS_ENABLE_RICH_TEXT_FORMATTING_KEY = "SETTINGS_ENABLE_RICH_TEXT_FORMATTING_KEY" private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY" private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY" private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY" @@ -759,6 +760,24 @@ class VectorPreferences @Inject constructor( } } + /** + * Tells if text formatting is enabled within the rich text editor. + * + * @return true if the text formatting is enabled + */ + fun isTextFormattingEnabled(): Boolean = + defaultPrefs.getBoolean(SETTINGS_ENABLE_RICH_TEXT_FORMATTING_KEY, true) + + /** + * Update whether text formatting is enabled within the rich text editor. + * + * @param isEnabled true to enable the text formatting + */ + fun setTextFormattingEnabled(isEnabled: Boolean) = + defaultPrefs.edit { + putBoolean(SETTINGS_ENABLE_RICH_TEXT_FORMATTING_KEY, isEnabled) + } + /** * Tells if a confirmation dialog should be displayed before staring a call. */ diff --git a/vector/src/main/res/drawable/ic_text_formatting.xml b/vector/src/main/res/drawable/ic_text_formatting.xml new file mode 100644 index 0000000000..375c459692 --- /dev/null +++ b/vector/src/main/res/drawable/ic_text_formatting.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_text_formatting_disabled.xml b/vector/src/main/res/drawable/ic_text_formatting_disabled.xml new file mode 100644 index 0000000000..bb34211c7a --- /dev/null +++ b/vector/src/main/res/drawable/ic_text_formatting_disabled.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml b/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml new file mode 100644 index 0000000000..7a22ab57f8 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_attachment_type_selector.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml index 9f49b8f9d6..c5afe1eb44 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -104,13 +104,26 @@ android:background="@drawable/bg_composer_rich_edit_text_single_line" /> + + + diff --git a/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml index 7aaa9f6a07..1a3023a805 100644 --- a/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml +++ b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml @@ -136,13 +136,29 @@ app:layout_constraintEnd_toEndOf="parent" /> + + + + + + () { fun givenCombinedLoginDisabled() { every { isOnboardingCombinedLoginEnabled() } returns false } + + fun givenLocationSharing(isEnabled: Boolean) { + every { isLocationSharingEnabled() } returns isEnabled + } + + fun givenVoiceBroadcast(isEnabled: Boolean) { + every { isVoiceBroadcastEnabled() } returns isEnabled + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt index 8b0630c24f..cd4f70bf63 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt @@ -40,4 +40,7 @@ class FakeVectorPreferences { fun givenIsClientInfoRecordingEnabled(isEnabled: Boolean) { every { instance.isClientInfoRecordingEnabled() } returns isEnabled } + + fun givenTextFormatting(isEnabled: Boolean) = + every { instance.isTextFormattingEnabled() } returns isEnabled } From 1007e02ceb7125e1a3ebd8181f32b13d7caff752 Mon Sep 17 00:00:00 2001 From: phardyle Date: Thu, 27 Oct 2022 03:36:25 +0000 Subject: [PATCH 0386/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.5% (2381 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- .../ui-strings/src/main/res/values-zh-rCN/strings.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index a7b0f702f9..7291650a7d 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -970,7 +970,7 @@ 文件%1$s 已被下载! 消息编辑 未找到编辑 - 过滤对话… + 过滤对话…… 找不到你要找的? 创建新房间 发送新私聊消息 @@ -1714,7 +1714,7 @@ 建议 已知用户 二维码 - 通过二维码添加 + 通过QR码添加 房间设置 主题 房间话题(可选) @@ -2419,7 +2419,7 @@ 我们会帮你建立连接 你会与谁聊最多? ${app_name}也非常适合工作场所。受到世界上最安全的组织信任。 - 聊天消息存储位置一律由您掌控,不受外来因素干扰。技术由 Matrix 提供。 + 选择保存你的对话的位置,给你控制权和独立性。通过Matrix连接。 安全且独立的通信,为你提供和在家中面对面对话同样等级的隐私。 安全传送消息。 向主服务器注册端点token失败: @@ -2632,4 +2632,9 @@ 启用富文本编辑器 折叠%s孩子 展开%s孩子 + 启用新的会话管理器 + 访问空间 + 欢迎使用新视图! + ⚠ 此房间里有未经验证的设备,它们将无法解密你发送的消息。 + 永远不要向这个房间里未经验证的会话发送加密的消息。 \ No newline at end of file From b00178c013ef03178ffeeab83fb7a401bfb54f79 Mon Sep 17 00:00:00 2001 From: Jingchao Feng Date: Tue, 25 Oct 2022 07:30:43 +0000 Subject: [PATCH 0387/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.5% (2381 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 7291650a7d..cc2ab4b3ca 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -2482,7 +2482,7 @@ 想要加入已有的服务器? 跳过此问题 家人和朋友 - 拥有你的对话。 + 拥有你的数据 位置 Threads Beta反馈 BETA From df16ec6cda5300677dbf5ea94f91de9514ee52b9 Mon Sep 17 00:00:00 2001 From: Nizami Date: Tue, 25 Oct 2022 09:55:37 +0000 Subject: [PATCH 0388/1068] Translated using Weblate (Azerbaijani) Currently translated at 2.7% (69 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/az/ --- library/ui-strings/src/main/res/values-az/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-az/strings.xml b/library/ui-strings/src/main/res/values-az/strings.xml index 53100db285..97e6d3def6 100644 --- a/library/ui-strings/src/main/res/values-az/strings.xml +++ b/library/ui-strings/src/main/res/values-az/strings.xml @@ -69,4 +69,11 @@ %1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s %1$s %2$s dəvətini geri götürdü. Səbəb: %3$s Otağ yaratdınız + Bu əməliyyatı yerinə yetirmək üçün sistem tənzimləmələrindən Kameraya icazə verin. + Tənzimləmələr + %1$s qoşuldu + Otağa qoşuldunuz + %1$s-ı dəvət etdiniz + Müzakirə yaratdınız + %1$s otağı yaratdı \ No newline at end of file From d7ebdbfdf536db1a8475ef20ea41bd9a880a30b9 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Tue, 25 Oct 2022 18:28:46 +0000 Subject: [PATCH 0389/1068] Translated using Weblate (Catalan) Currently translated at 100.0% (2519 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- library/ui-strings/src/main/res/values-ca/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 87be4a96a5..f3e064d2f3 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2814,4 +2814,11 @@ Activa l\'editor de text enriquit Rep notificacions en aquesta sessió. Notificacions + Carregant + Pausa l\'emissió de veu + Reprodueix o reprèn l\'emissió de veu + Atura l\'enregistrament d\'emissió de veu + Pausa l\'enregistrament d\'emissió de veu + Reprèn l\'enregistrament d\'emissió de veu + En directe \ No newline at end of file From 0ef9e5358f1c058da92d08a91c43170d3d5242c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 26 Oct 2022 07:25:02 +0000 Subject: [PATCH 0390/1068] Translated using Weblate (Estonian) Currently translated at 99.6% (2511 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 05b01c2bde..b0e2a59636 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2805,4 +2805,11 @@ Teine seade on juba võrku loginud. Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade; Päring ei õnnestunud. + Andmed on puhverdamisel + Alusta või jätka ringhäälingukõne esitamist + Lõpeta ringhäälingukõne salvestamine + Peata ringhäälingukõne salvestamine + Jätka ringhäälingukõne salvestamist + Peata ringhäälingukõne esitamine + Otse eetris \ No newline at end of file From e185bdf8e7c6ef02ca670c5067b997e429f2b85a Mon Sep 17 00:00:00 2001 From: Roel ter Maat Date: Wed, 26 Oct 2022 14:03:37 +0000 Subject: [PATCH 0391/1068] Translated using Weblate (Dutch) Currently translated at 97.9% (2468 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nl/ --- .../src/main/res/values-nl/strings.xml | 1188 +++++++++-------- 1 file changed, 641 insertions(+), 547 deletions(-) diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index e8ede9b079..46d5f9a675 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -2,7 +2,7 @@ Uitnodiging van %s %1$s heeft %2$s uitgenodigd - %1$s heeft u uitgenodigd + %1$s heeft je uitgenodigd %1$s is deelnemer geworden van de kamer %1$s heeft het de kamer verlaten %1$s heeft de uitnodiging geweigerd @@ -47,7 +47,7 @@ \nKamers importeren Initiële synchronisatie: \nGesprekken worden geladen -\nAls u aan veel kamers deelneemt kan dit even duren +\nAls je aan veel kamers deelneemt kan dit even duren Initiële synchronisatie: \nUitgenodigde kamers worden geïmporteerd Initiële synchronisatie: @@ -59,7 +59,7 @@ %1$s heeft de uitnodiging voor %2$s om deelnemer te worden van de kamer ingetrokken Uitnodiging van %1$s. Reden: %2$s %1$s heeft %2$s uitgenodigd. Reden: %3$s - %1$s heeft u uitgenodigd. Reden: %2$s + %1$s heeft je uitgenodigd. Reden: %2$s %1$s neemt nu deel. Reden: %2$s %1$s is weggegaan. Reden: %2$s %1$s heeft de uitnodiging geweigerd. Reden: %2$s @@ -81,8 +81,8 @@ %1$s heeft het hoofdadres voor dit gesprek verwijderd. %1$s heeft gasten de toegang tot dit gesprek verleend. %1$s heeft gasten de toegang tot het gesprek verhinderd. - %1$s heeft end-to-end-versleuteling ingeschakeld. - %1$s heeft end-to-end-versleuteling ingeschakeld (onbekend algoritme %2$s). + %1$s heeft eind-tot-eind-versleuteling ingeschakeld. + %1$s heeft eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %2$s). Instellingen Oké Annuleren @@ -124,14 +124,14 @@ Crash-logboek versturen Schermafdruk versturen Probleem melden - Beschrijf de fout. Wat heeft u gedaan\? Wat verwachtte u dat er zou gebeuren\? Wat is er echt gebeurd\? - Beschrijf hier uw probleem - Om het probleem te kunnen onderzoeken worden logboeken van deze cliënt met de foutmelding verstuurd. Deze foutmelding, inclusief de logboeken en schermafdruk, zullen niet openbaar zichtbaar zijn. Indien u liever alleen de bovenstaande tekst verstuurt, haal dan het vinkje weg: - Het ziet er naar uit dat u de telefoon in frustratie schudt. Wilt u een probleem melden\? + Beschrijf de fout. Wat heb je gedaan\? Wat verwachtte je dat er zou gebeuren\? Wat is er echt gebeurd\? + Beschrijf hier jouw probleem + Om het probleem te kunnen onderzoeken worden logboeken van deze cliënt met de foutmelding verstuurd. Deze foutmelding, inclusief de logboeken en schermafdruk, zullen niet openbaar zichtbaar zijn. Indien je liever alleen de bovenstaande tekst verstuurt, haal dan het vinkje weg: + Het ziet er naar uit dat je de telefoon in frustratie schudt. Wil je een probleem melden\? De foutmelding is verzonden Versturen van foutmelding is mislukt (%s) Voortgang (%s%%) - De toepassing is de vorige keer gecrasht. Wilt u dit melden\? + De toepassing is de vorige keer gecrasht. Wil je dit melden\? Deelnemen aan kamer Inlognaam Afmelden @@ -147,8 +147,8 @@ Dit is geen geldig e-mailadres Dit e-mailadres is al in gebruik. Wachtwoord vergeten? - Deze server wil graag weten of u geen robot bent - Verifiëren van het e-mailadres is mislukt: zorg dat u op de koppeling in de e-mail hebt geklikt + Deze server wil graag weten of je geen robot bent + Verifiëren van het e-mailadres is mislukt: zorg dat je op de koppeling in de e-mail hebt geklikt Voer een geldige URL in Ongeldige JSON Bevatte geen geldige JSON @@ -164,8 +164,8 @@ Oproep gaande… De andere kant heeft niet opgenomen. Informatie - ${app_name} heeft toegang nodig tot uw microfoon om spraakoproepen te maken. - ${app_name} heeft toegang nodig tot uw camera en microfoon om video-oproepen te maken. + ${app_name} heeft toegang nodig tot je microfoon om spraakoproepen te maken. + ${app_name} heeft toegang nodig tot je camera en microfoon om video-oproepen te maken. \n \nVerleen toegang op de volgende pop-ups om de oproep te maken. JA @@ -176,7 +176,7 @@ Afwijzen Naar ongelezen springen Gesprek verlaten - Weet u zeker dat u het gesprek wilt verlaten\? + Weet je zeker dat je het gesprek wil verlaten\? TWEEGESPREKKEN Uitnodigen Verbannen @@ -184,22 +184,22 @@ Alle berichten van deze persoon verbergen Alle berichten van deze persoon tonen Vermelden - U kunt deze veranderingen niet ongedaan maken aangezien u de persoon tot hetzelfde niveau als uzelf promoveert. -\nWeet u het zeker\? + Je kan deze veranderingen niet ongedaan maken aangezien je de persoon tot hetzelfde niveau als jezelf promoveert. +\nWeet je het zeker\? %s is aan het typen… %1$s en %2$s zijn aan het typen… %1$s, %2$s en anderen zijn aan het typen… - U heeft geen toestemming om dit naar dit gesprek te sturen. + Je hebt geen toestemming om dit naar dit gesprek te sturen. Vertrouwen Niet vertrouwen Afmelden Negeren Vingerafdruk (%s): Kan de identiteit van de externe server niet verifïeren. - Dit kan betekenen dat iemand uw internetverkeer met slechte bedoelingen probeert te onderscheppen, of dat uw telefoon het certificaat van de server niet vertrouwt. + Dit kan betekenen dat iemand jouw internetverkeer met slechte bedoelingen probeert te onderscheppen, of dat jouw telefoon het certificaat van de server niet vertrouwt. Als de serverbeheerder heeft gezegd dat dit normaal is, wees er dan zeker van dat de vingerafdruk hieronder overeenkomt met de door de beheerder verschafte vingerafdruk. - Het certificaat is veranderd van één dat door uw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. - Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Contacteer de serverbeheerder voor de verwachte vingerafdruk. + Het certificaat is veranderd van één dat door jouw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. + Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Neem contact op met de serverbeheerder voor de verwachte vingerafdruk. Aanvaard het certificaat alleen als de serverbeheerder een vingerafdruk heeft gepubliceerd die overeenkomt met degene hierboven. Zoeken Gespreksleden filteren @@ -249,14 +249,14 @@ Aangemeld als Server Identiteitsserver - Bekijk uw e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. + Bekijk je e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. Dit e-mailadres is al in gebruik. Dit telefoonnummer is al in gebruik. Wachtwoord veranderen Huidig wachtwoord Nieuw wachtwoord Bijwerken van wachtwoord is mislukt - Uw wachtwoord is gewijzigd + Je wachtwoord is gewijzigd Alle berichten van %s tonen\? Kies een land Onderwerp @@ -270,7 +270,7 @@ Geavanceerd Interne ID van dit gesprek Experimenteel - Dit zijn experimentele functies die zich op onverwachte manieren kunnen gedragen. Wees behoedzaam bij het gebruik van deze functies. + Dit zijn experimentele functionaliteiten die zich op onverwachte manieren kunnen gedragen. Wees behoedzaam bij het gebruik van deze functies. Instellen als hoofdadres Niet instellen als hoofdadres Ontsleutelingsfout @@ -292,8 +292,8 @@ NIET geverifieerd Geverifieerd Verifiëren - Om te verifiëren dat deze sessie vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar persoonsinstellingen van deze sessie overeenkomt met de sleutel hieronder: - Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. + Om te verifiëren dat deze sessie vertrouwd kan worden, neem je contact op met de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraag je ze of de sleutel die ze zien in hun persoonsinstellingen van deze sessie overeenkomt met de sleutel hieronder: + Als het overeenkomt, druk je op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en kan je het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. Kamermap kiezen Servernaam Alle gesprekken op server %s @@ -324,12 +324,12 @@ Luisteren naar gebeurtenissen Meldingsgeluid Tijdsaanduidingen in 12-uursformaat weergeven - Weet u zeker dat u deze widget uit dit gesprek wilt verwijderen\? + Weet je zeker dat je deze widget uit dit gesprek wilt verwijderen\? Kan widget niet aanmaken. Versturen van verzoek mislukt. Het machtsniveau moet een positief geheel getal zijn. - U zit niet in dit gesprek. - U heeft geen toestemming om dat in dit gesprek te doen. + Je zit niet in dit gesprek. + Je hebt geen toestemming om dat in dit gesprek te doen. room_id ontbreekt in het verzoek. user_id ontbreekt in het verzoek. Gesprek %s is niet zichtbaar. @@ -344,8 +344,8 @@ Berichten die mijn inlognaam bevatten Statistische gegevens Systeemcamera gebruiken - U heeft een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. - Uw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. + Je hebt een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. + Jouw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. Verificatie starten Opdrachtfout Onbekende opdracht: %s @@ -354,8 +354,8 @@ Versleuteld bericht Laden… Schudden om een probleem te melden - Weet u zeker dat u een spraakoproep wilt beginnen\? - Weet u zeker dat u een video-oproep wilt beginnen\? + Weet je zeker dat je een spraakoproep wilt beginnen\? + Weet je zeker dat je een video-oproep wilt beginnen\? %d verandering in lidmaatschap %d veranderingen in lidmaatschap @@ -365,7 +365,7 @@ %d deelnemer %d deelnemers - Als een persoon wordt verbannen, wordt deze uit deze kamer verwijderd en wordt er voorkomen dat hij opnieuw lid wordt. + Als een persoon wordt verbannen, wordt deze uit deze kamer verwijderd en wordt er voorkomen dat ze opnieuw lid worden. %d nieuw bericht %d nieuwe berichten @@ -387,40 +387,40 @@ Thuis Gesprekken Uitgenodigd - %2$s heeft u uit %1$s gezet - %2$s heeft u uit %1$s verbannen + %2$s heeft je uit %1$s gezet + %2$s heeft je uit %1$s verbannen Reden: %1$s Avatar - %d ongelezen bericht waarin u vermeld bent - %d ongelezen berichten waarin u vermeld bent + %d ongelezen bericht waarin je vermeld bent + %d ongelezen berichten waarin je vermeld bent Verstuur een sticker Sticker versturen - U heeft momenteel geen stickerpakketten ingeschakeld. + Je hebt momenteel geen stickerpakketten ingeschakeld. \n -\nWilt u er nu een paar toevoegen\? +\nWil je er nu een paar toevoegen\? Account deactiveren Mijn account deactiveren Statistische gegevens (analytics) versturen ${app_name} verzamelt anonieme statistische gegevens (analytics) om het voor ons mogelijk te maken om de app te verbeteren. Er ontbreekt een vereiste parameter. - Om de %1$s-server verder te blijven gebruiken, dient u de voorwaarden te lezen en ermee akkoord te gaan. + Om de %1$s-server verder te blijven gebruiken, dien je de voorwaarden te lezen en ermee akkoord te gaan. Nu doorlezen Account deactiveren - Dit zal uw account voorgoed onbruikbaar maken. U zult zich niet meer kunnen aanmelden, en niemand anders zal met dezelfde persoon-ID kunnen registreren. Dit zal er voor zorgen dat uw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert de accountgegevens van de identiteitsserver. Deze actie is onomkeerbaar. + Dit zal je account voorgoed onbruikbaar maken. Je zal je niet meer kunnen aanmelden, en niemand anders zal met dezelfde persoon-ID kunnen registreren. Dit zal er voor zorgen dat jouw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert de accountgegevens van de identiteitsserver. Deze actie is onomkeerbaar. \n -\nHet deactiveren van uw account zal er niet standaard voor zorgen dat de berichten die u hebt verzonden worden vergeten. Indien u wilt dat wij de berichten vergeten, vinkt u het vakje hieronder aan. +\nHet deactiveren van je account zal er niet standaard voor zorgen dat de berichten die je hebt verzonden worden vergeten. Indien je wil dat wij de berichten vergeten, vink je het vakje hieronder aan. \n -\nDe zichtbaarheid van berichten in Matrix is gelijkaardig aan e-mails. Het vergeten van uw berichten betekent dat berichten die u verstuurd heeft niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan. +\nDe zichtbaarheid van berichten in Matrix is gelijkaardig aan e-mails. Het vergeten van jouw berichten betekent dat berichten die je verstuurd hebt niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan. Vergeet alle berichten die ik heb verstuurd wanneer mijn account gedeactiveerd is (Let op: dit zal er voor zorgen dat toekomstige personen een onvolledig beeld krijgen van gesprekken) Account deactiveren Downloaden - Beveiligingssleutels van uw sessies opnieuw aanvragen. + Beveiligingssleutels van je sessies opnieuw aanvragen. Start ${app_name} op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels naar deze sessie kan sturen. Spraakbericht versturen Sorry, er is geen externe toepassing gevonden om deze actie te voltooien. - Voer uw wachtwoord in. + Voer je wachtwoord in. Beschrijf het probleem in het Engels, indien mogelijk. Media bekijken vóór het versturen Geeft activiteit weer @@ -433,7 +433,7 @@ Gesprek verlaten Onderwerp van het gesprek instellen Stuurt persoon met gegeven ID eruit - Wijzigt uw weergavenaam + Wijzig je weergavenaam Markdown aan/uit Dit gesprek is vervangen en is niet langer actief. Het gesprek wordt hier voortgezet @@ -445,7 +445,7 @@ %d geselecteerd Om Matrix-appbeheer te herstellen - contact op te nemen met uw dienstbeheerder + contact op te nemen met je dienstbeheerder Deze server heeft een van zijn bronlimieten overschreden, dus sommige personen zullen zich niet kunnen aanmelden. Deze server heeft een van zijn bronlimieten overschreden. Deze server heeft zijn limiet voor maandelijks actieve personen overschreden, dus sommige personen zullen zich niet kunnen aanmelden. @@ -460,11 +460,11 @@ Beltoon voor inkomende oproepen Selecteer beltoon voor oproepen: Eruit sturen - Voorvertoning van koppelingen in het gesprek tonen (als uw server deze functie ondersteunt). + Voorvertoning van koppelingen in het gesprek tonen (als je server deze functionaliteit ondersteunt). Typmeldingen versturen - Laat andere personen weten dat u aan het typen bent. + Laat andere personen weten dat je aan het typen bent. Markdown-opmaak - Maak berichten op met Markdown-syntax voordat ze verstuurd worden. Hiermee kunt u uitgebreide opmaak gebruiken, zoals sterretjes voor schuingedrukte tekst. + Maak berichten op met Markdown-syntax voordat ze verstuurd worden. Hiermee kan je uitgebreide opmaak gebruiken, zoals sterretjes voor schuingedrukte tekst. Leesbevestigingen weergeven Tik op de leesbevestigingen voor een uitgebreide lijst. Toetredingen en verlatingen weergeven @@ -473,18 +473,18 @@ Omvat veranderingen in avatar en weergavenaam. Sleutelback-up Sleutelback-up gebruiken - Indien u zich nu afmeldt, zult u uw versleutelde berichten verliezen - Sleutelback-up is bezig. Indien u zich nu afmeldt, zult u de toegang tot uw versleutelde berichten verliezen. - Veilige sleutelback-up dient actief te zijn op al uw sessies om de toegang tot uw versleutelde berichten niet te verliezen. + Indien je jezelf nu afmeldt, zal je jouw versleutelde berichten verliezen + Sleutelback-up is bezig. Indien je jezelf nu afmeldt, zal je de toegang tot jouw versleutelde berichten verliezen. + Veilige sleutelback-up dient actief te zijn op al je sessies om de toegang tot je versleutelde berichten niet te verliezen. Ik wil mijn versleutelde berichten niet Sleutels worden geback-upt… - Weet u het zeker\? + Weet je het zeker\? Back-up maken - U zult de toegang tot uw versleutelde berichten verliezen, tenzij u eerst een back-up van uw sleutels maakt vooraleer u zich afmeldt. + Je zal de toegang tot je versleutelde berichten verliezen, tenzij je eerst een back-up van je sleutels maakt voordat je jezelf afmeldt. Overslaan Klaar Negeren - Weet u zeker dat u zich wilt afmelden\? + Weet je zeker dat je jezelf wilt afmelden\? Markeren als gelezen Aanmelden met unieke aanmelding Video-oproep gaande… @@ -494,7 +494,7 @@ Diagnostische probleemoplossingsinformatie Testen uitvoeren Bezig met uitvoeren… (%1$d van %2$d) - Basisdiagnose is oké. Als u nog steeds geen meldingen ontvangt, gelieve dan een bugmelding in te dienen om ons te helpen onderzoeken. + Basisdiagnose is oké. Als je nog steeds geen meldingen ontvangt, gelieve dan een bugmelding in te dienen om ons te helpen onderzoeken. Er zijn één of meer tests mislukt, probeer de aanbevolen oplossing(en). Er zijn één of meer tests mislukt, gelieve een bugmelding in te dienen om ons te helpen onderzoeken. Systeeminstellingen. @@ -503,8 +503,8 @@ \nGelieve deze te controleren. Instellingen openen Accountinstellingen. - Meldingen zijn ingeschakeld voor uw account. - Meldingen zijn uitgeschakeld voor uw account. + Meldingen zijn ingeschakeld voor jouw account. + Meldingen zijn uitgeschakeld voor jouw account. \nGelieve de accountinstellingen te controleren. Inschakelen Sessie-instellingen. @@ -514,7 +514,7 @@ Inschakelen Aangepaste instellingen. Sommige soorten berichten zijn stil (ze geven een geluidsloze melding). - Sommige meldingen zijn uitgeschakeld in uw aangepaste instellingen. + Sommige meldingen zijn uitgeschakeld in je aangepaste instellingen. Play-diensten controleren De APK van Google Play Services is beschikbaar en up-to-date. ${app_name} maakt gebruikt van Google Play Services om pushberichten af te leveren, maar dit lijkt niet juist geconfigureerd te zijn: @@ -528,7 +528,7 @@ [%1$s] \nDeze fout is onafhankelijk van ${app_name}. Volgens Google betekent deze fout dat het apparaat te veel apps heeft geregistreerd met FCM. De fout treedt enkel op ingeval er een enorm aantal apps is, dus zou dit de gemiddelde persoon niet mogen hinderen. [%1$s] -\nDeze fout is onafhankelijk van ${app_name}. Ze kan verschillende oorzaken hebben. Misschien werkt het als u het later opnieuw probeert. U kunt ook controleren of het gegevensverbruik van Google Play Services niet wordt beperkt in de systeeminstellingen, of dat de klok van uw apparaat wel juist staat, of dat het misschien aan een aangepaste ROM ligt. +\nDeze fout is onafhankelijk van ${app_name}. Ze kan verschillende oorzaken hebben. Misschien werkt het als je het later opnieuw probeert. Je kan ook controleren of het gegevensverbruik van Google Play Services niet wordt beperkt in de systeeminstellingen, of dat de klok van je apparaat wel juist staat, of dat het misschien aan een aangepaste ROM ligt. [%1$s] \nDeze fout is onafhankelijk van ${app_name}. Er is geen Google-account verbonden met de telefoon. Open het accountbeheer en voeg er een Google-account toe. Account toevoegen @@ -538,13 +538,13 @@ \n%1$s Starten bij opstarten van apparaat De dienst zal starten wanneer het apparaat wordt herstart. - De dienst zal niet starten wanneer het apparaat wordt herstart en u zult geen meldingen ontvangen tot u ${app_name} hebt geopend. + De dienst zal niet starten wanneer het apparaat wordt herstart en je zal geen meldingen ontvangen tot je ${app_name} hebt geopend. Starten bij opstarten inschakelen Achtergrondbeperkingen controleren Achtergrondbeperkingen zijn uitgeschakeld voor ${app_name}. Deze test dient uitgevoerd te worden met een mobiele verbinding (geen wifi). \n%1$s Achtergrondbeperkingen zijn ingeschakeld voor ${app_name}. -\nAl wat de app probeert te doen zal in de achtergrond hevig beperkt worden; dit kan het correct functioneren van meldingen beïnvloeden. +\nAlles wat de app probeert te doen zal in de achtergrond hevig beperkt worden; dit kan het correct functioneren van meldingen beïnvloeden. \n%1$s Beperkingen uitschakelen Accuoptimalisatie @@ -566,7 +566,7 @@ Standaardmediabron Kiezen Sluitergeluid afspelen - Maak een wachtwoord aan om de geëxporteerde sleutels mee te versleutelen. U heeft dit wachtwoord nodig om de sleutels te kunnen importeren. + Maak een wachtwoord aan om de geëxporteerde sleutels mee te versleutelen. Je hebt dit wachtwoord nodig om de sleutels te kunnen importeren. Herstel van versleutelde berichten Sleutelback-up beheren @@ -599,27 +599,27 @@ Wachtwoorden komen niet overeen Voer een wachtwoord in Wachtwoord is te zwak - Verwijder het wachtwoord als u wilt dat ${app_name} een herstelsleutel genereert. - Verlies nooit uw versleutelde berichten - Berichten in versleutelde gesprekken worden beveiligd met end-to-end-versleuteling. Enkel de ontvanger(s) en u hebben de sleutels om deze berichten te lezen. + Verwijder het wachtwoord als je wil dat ${app_name} een herstelsleutel genereert. + Verlies nooit jouw versleutelde berichten + Berichten in versleutelde gesprekken worden beveiligd met eind-to-eind-versleuteling. Enkel de ontvanger(s) en jij hebben de sleutels om deze berichten te lezen. \n -\nMaak een veilige back-up van uw sleutels om ze niet te verliezen. +\nMaak een veilige back-up van jouw sleutels om ze niet te verliezen. Begin sleutelback-up te gebruiken (Geavanceerd) Sleutels handmatig exporteren - Beveilig uw back-up met een wachtwoord. - We bewaren een versleutelde kopie van uw sleutels op onze server. Bescherm uw back-up met een wachtwoord om deze veilig te houden. + Beveilig je back-up met een wachtwoord. + We bewaren een versleutelde kopie van jouw sleutels op onze server. Bescherm je back-up met een wachtwoord om deze veilig te houden. \n -\nVoor een maximale beveiliging zou deze sleutel moeten verschillen van uw accountwachtwoord. +\nVoor een maximale beveiliging zou deze sleutel moeten verschillen van je accountwachtwoord. Wachtwoord instellen Back-up wordt aangemaakt - Of beveilig uw back-up met een herstelsleutel, en bewaar deze op een veilige plaats. + Of beveilig je back-up met een herstelsleutel, en bewaar deze op een veilige plaats. (Geavanceerd) Instellen met herstelsleutel Klaar! - Uw sleutels worden geback-upt. - Uw herstelsleutel is een veiligheidsnet - u kunt deze gebruiken om de toegang tot uw versleutelde berichten te herstellen indien u uw wachtwoord vergeet. -\nBewaar uw herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) - Bewaar uw herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) + Jouw sleutels worden geback-upt. + Jouw herstelsleutel is een veiligheidsnet - je kan deze gebruiken om de toegang tot jouw versleutelde berichten te herstellen indien je jouw wachtwoord vergeet. +\nBewaar je herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) + Bewaar je herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) Klaar Ik heb een kopie gemaakt Herstelsleutel opslaan @@ -630,23 +630,23 @@ Herstelsleutel wordt gegenereerd met wachtwoord, dit proces kan enkele seconden duren. Herstelsleutel Onverwachte fout - Weet u het zeker\? - U kunt de toegang tot uw berichten verliezen indien u zich afmeldt of dit apparaat verliest. + Weet je het zeker\? + Je kunt de toegang tot je berichten verliezen indien je jezelf afmeldt of dit apparaat verliest. Back-upversie wordt opgehaald… - Gebruik uw herstelwachtwoord om uw versleutelde berichtgeschiedenis te ontgrendelen - uw herstelsleutel gebruiken - Als u uw herstelwachtwoord niet meer weet, kunt u %s. - Gebruik uw herstelsleutel om uw versleutelde berichtgeschiedenis te ontgrendelen + Gebruik je herstelwachtwoord om jouw versleutelde berichtgeschiedenis te ontgrendelen + jouw herstelsleutel gebruiken + Als je jouw herstelwachtwoord niet meer weet, kan je %s. + Gebruik je herstelsleutel om jouw versleutelde berichtgeschiedenis te ontgrendelen Voer de herstelsleutel in - Herstelsleutel verloren\? U kunt er een nieuwe instellen in de instellingen. - De back-up kan met dit wachtwoord niet ontsleuteld worden: controleer of u het juiste herstelwachtwoord heeft ingevoerd. + Herstelsleutel verloren\? Je kan er een nieuwe instellen in de instellingen. + De back-up kan met dit wachtwoord niet ontsleuteld worden: controleer of je het juiste herstelwachtwoord hebt ingevoerd. Back-up wordt hersteld: Herstelsleutel wordt berekend… Sleutels worden gedownload… Sleutels worden geïmporteerd… Geschiedenis ontgrendelen Voer een herstelsleutel in - De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of u de juiste herstelsleutel heeft ingevoerd. + De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of je de juiste herstelsleutel hebt ingevoerd. Back-up hersteld %s! Back-up met %d sleutel hersteld. @@ -661,18 +661,18 @@ Back-up verwijderen Sleutelback-up is correct ingesteld voor deze sessie. Sleutelback-up is niet actief op deze sessie. - Uw sleutels worden niet geback-upt vanaf deze sessie. + Jouw sleutels worden niet geback-upt vanaf deze sessie. De back-up heeft een ondertekening van een onbekende sessie met ID %s. De back-up heeft een geldige ondertekening van deze sessie. De back-up heeft een geldige ondertekening van de geverifieerde sessie %s. De back-up heeft een geldige ondertekening van de ongeverifieerde sessie %s De back-up heeft een ongeldige ondertekening van de geverifieerde sessie %s De back-up heeft een ongeldige ondertekening van de ongeverifieerde sessie %s - Herstel nu met uw wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. + Herstel nu met je wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. Back-up wordt verwijderd… Back-up verwijderen - Uw geback-upte versleutelingssleutels verwijderen van de server\? U zult uw herstelsleutel niet meer kunnen gebruiken om de versleutelde berichtgeschiedenis te lezen. - Verlies nooit uw versleutelde berichten + Jouw geback-upte versleutelingssleutels verwijderen van de server\? Je zal jouw herstelsleutel niet meer kunnen gebruiken om de versleutelde berichtgeschiedenis te lezen. + Verlies nooit je versleutelde berichten Sleutelback-up gebruiken Nieuwe sleutels voor versleutelde berichten Beheren in sleutelback-up @@ -687,21 +687,21 @@ Ondertekening Sorry, vergadergesprekken met Jitsi worden nog niet ondersteund op oudere apparaten (met een Android-versie lager dan 6.0) onbekend IP-adres - Een nieuwe sessie vraagt versleutelingssleutels aan. -\nSessienaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. - Een ongeverifieerde sessie vraagt versleutelingssleutels aan. -\nSessienaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. + Een nieuwe sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls je jezelf niet hebt aangemeld op een andere sessie, negeer dan dit verzoek. + Een ongeverifieerde sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls je jezelf niet hebt aangemeld op een andere sessie, negeer dan dit verzoek. Delen Sleuteldeelverzoek Negeren Geverifieerd! Ik snap het Verificatieverzoek - %s wil uw sessie verifiëren + %s wil je sessie verifiëren Onbekende fout Geen Intrekken @@ -712,17 +712,17 @@ Synchroniseren op de achtergrond Geoptimaliseerd voor batterij ${app_name} zal op een batterijzuinige manier synchroniseren op de achtergrond. -\nAfhankelijk van de staat van uw apparaat kan het besturingssysteem de synchronisatie uitstellen. +\nAfhankelijk van de staat van je apparaat kan het besturingssysteem de synchronisatie uitstellen. Geoptimaliseerd voor snelheid ${app_name} zal periodiek op de achtergrond synchroniseren (configureerbaar). -\nDit heeft een negatieve impact op uw batterij- en datagebruik. Er zal een melding getoond worden ter informatie. +\nDit heeft een negatieve impact op je batterij- en datagebruik. Er zal een melding getoond worden ter informatie. Geen achtergrondssynchronisatie - U zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. + Je zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. Integraties Gebruik een integratiebeheerder om bots, bruggen, widgets en stickerpakketten te beheren. -\nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens u. +\nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens jou. Ontdekken - Beheer uw ontdekkingsinstellingen. + Beheer jouw ontdekkingsinstellingen. Integraties toestaan Integratiebeheerder Widget @@ -735,10 +735,10 @@ Widget herladen Openen in browser Toegang intrekken voor mij - Uw weergavenaam - Uw profielfoto-URL - Uw persoon-ID - Uw thema + Jouw weergavenaam + Jouw profielfoto-URL + Jouw persoon-ID + Jouw thema Widget-ID Gespreks-ID Deze widget wil gebruik maken van de volgende bronnen: @@ -747,25 +747,25 @@ Camera gebruiken Microfoon gebruiken DRM-beschermde media lezen - Om verder te gaan dient u de dienstvoorwaarden te aanvaarden. - Er bestaat al een back-up op uw server - Het lijkt erop dat u al een back-up van uw herstelsleutel heeft uit een andere sessie. Wilt u deze vervangen door degene die u nu aanmaakt\? + Om verder te gaan dien je de dienstvoorwaarden te aanvaarden. + Er bestaat al een back-up op je server + Het lijkt erop dat je al een back-up van je herstelsleutel heeft uit een andere sessie. Wilt je deze vervangen door degene die je nu aanmaakt\? Vervangen Stoppen Back-upstatus wordt gecontroleerd - U gebruikt geen identiteitsserver - Het lijkt er op dat u probeert verbinding te maken met een andere server. Wil je uitloggen\? + Je gebruikt geen identiteitsserver + Het lijkt er op dat je probeert verbinding te maken met een andere server. Wil je uitloggen\? Bewerken Beantwoorden Opnieuw proberen - Heeft u een uitnodiging gestuurd + Heeft je een uitnodiging gestuurd Uitgenodigd door %s - U bent helemaal bij! - U hebt geen ongelezen berichten meer + Je bent helemaal bij! + Je hebt geen ongelezen berichten meer Gesprekken - Uw directe gesprekken zullen hier worden weergegeven. Gebruik de + knop rechts onder om een gesprek te starten. + Jouw directe gesprekken zullen hier worden weergegeven. Gebruik de + knop rechts onder om een gesprek te starten. Kamers - Uw kamers zullen hier worden weergegeven. Gebruik de + knop rechtsonder om een bestaande kamer te openen of een nieuwe aan te maken. + Jouw kamers zullen hier worden weergegeven. Gebruik de + knop rechtsonder om een bestaande kamer te openen of een nieuwe aan te maken. Reacties Bevestigen Reactie Toevoegen @@ -775,7 +775,7 @@ Gebeurtenis gemodereerd door gesprek beheerder Niet correcte gebeurtenis, kan niet weergeven Nieuwe kamer aanmaken - Geen netwerk. Controleer uw internet verbinding. + Geen netwerk. Controleer je internet verbinding. Wijzigen Netwerk wijzigen Even wachten… @@ -787,8 +787,8 @@ Publiek Iedereen kan deelnemer worden van deze kamer Afspelen - U heeft het hoofdadres voor dit gesprek verwijderd. - U heeft %1$s uitgenodigd. Reden: %2$s + Je hebt het hoofdadres voor dit gesprek verwijderd. + Je hebt %1$s uitgenodigd. Reden: %2$s Jouw uitnodiging. Reden: %1$s Bericht verstuurd Initiële synchronisatie: @@ -797,36 +797,36 @@ \nAan het wachten op een antwoord van de server… Lege kamer (was %s) Moderator - U heeft %1$s uitgenodigd + Je hebt %1$s uitgenodigd %1$s nodigde %2$s uit Geen verandering. - U heeft toekomstige berichten zichtbaar gemaakt voor %1$s + Je hebt toekomstige berichten zichtbaar gemaakt voor %1$s %1$s heeft toekomstige berichten zichtbaar gemaakt voor %2$s - U hebt de oproep beëindigd. - U hebt de oproep beantwoord. - U heeft uw schermnaam gewijzigd van %1$s naar %2$s - U heeft uw schermnaam ingesteld op %1$s - U heeft uw avatar aangepast - U heeft de uitnodiging geweigerd - U heeft de kamer verlaten + Je hebt de oproep beëindigd. + Je hebt de oproep beantwoord. + Je hebt je schermnaam gewijzigd van %1$s naar %2$s + Je hebt je schermnaam ingesteld op %1$s + Je hebt je avatar aangepast + Je hebt de uitnodiging geweigerd + Je hebt de kamer verlaten %1$s heeft de kamer verlaten - U heeft de kamer verlaten - U heeft %1$s uitgenodigd - U heeft de discussie aangemaakt + Je hebt de kamer verlaten + Je hebt %1$s uitgenodigd + Je hebt de discussie aangemaakt %1$s heeft de discussie aangemaakt - U heeft de kamer aangemaakt + Je hebt de kamer aangemaakt %1$s heeft de kamer aangemaakt - Uw uitnodiging - U heeft %1$s verbannen. Reden: %2$s - U heeft de verbanning van %1$s opgeheven. Reden: %2$s - U heeft %1$s eruit getrapt. Reden: %2$s - U heeft de uitnodiging geweigerd. Reden: %1$s - U bent vertrokken. Reden: %1$s + Je uitnodiging + Je hebt %1$s verbannen. Reden: %2$s + Je hebt de verbanning van %1$s opgeheven. Reden: %2$s + Je hebt %1$s eruit getrapt. Reden: %2$s + Je hebt de uitnodiging geweigerd. Reden: %1$s + Je bent vertrokken. Reden: %1$s %1$s is vertrokken. Reden: %2$s - U heeft de kamer verlaten. Reden: %1$s - U heeft zich aangesloten. Reden: %1$s + Je hebt de kamer verlaten. Reden: %1$s + Je hebt je aangesloten. Reden: %1$s %1$s heeft zich aangesloten. Reden: %2$s - U heeft zich aangesloten bij de kamer. Reden: %1$s + Je hebt je aangesloten bij de kamer. Reden: %1$s %1$s, %2$s, %3$s en %4$d andere %1$s, %2$s, %3$s en %4$d anderen @@ -835,75 +835,75 @@ %1$s, %2$s en %3$s %1$s van %2$s naar %3$s %1$s heeft het machtigingsniveau van %2$s aangepast. - U heeft het machtigingsniveau van %1$s aangepast. + Je hebt het machtigingsniveau van %1$s aangepast. Speciaal Speciaal (%1$d) Standaardlid Beheerder - U heeft de widget %1$s aangepast + Je hebt de widget %1$s aangepast %1$s heeft de widget %2$s aangepast - U heeft de widget %1$s verwijderd + Je hebt de widget %1$s verwijderd %1$s heeft de widget %2$s verwijderd - U heeft de widget %1$s toegevoegd + Je hebt de widget %1$s toegevoegd %1$s heeft de widget %2$s toegevoegd - U heeft de uitnodiging voor %1$s geaccepteerd - U heeft de uitnodiging voor %1$s ingetrokken + Je hebt de uitnodiging voor %1$s geaccepteerd + Je hebt de uitnodiging voor %1$s ingetrokken %1$s heeft de uitnodiging voor %2$s ingetrokken - U heeft de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten - U heeft een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten - U heeft de kameravatar verwijderd + Je hebt de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten + Je hebt een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten + Je hebt de kameravatar verwijderd %1$s heeft de kameravatar verwijderd - U heeft het kameronderwerp verwijderd - U heeft de kamernaam verwijderd - U heeft de kamer geüpgraded. - U verstuurde data om het gesprek op te zetten. + Je hebt het kameronderwerp verwijderd + Je hebt de kamernaam verwijderd + Je hebt de kamer geüpgraded. + Je verstuurde data om het gesprek op te zetten. %s verstuurde data om het gesprek op te zetten. - U heeft een audiogesprek geopend. - U heeft een videogesprek geopend. - U heeft de kamernaam veranderd naar: %1$s - U heeft de kamerafbeelding aangepast + Je hebt een audiogesprek geopend. + Je hebt een videogesprek geopend. + Je hebt de kamernaam veranderd naar: %1$s + Je hebt de kamerafbeelding aangepast %1$s heeft de kamerafbeelding aangepast - U heeft het onderwerp gewijzigd naar: %1$s - U heeft uw weergavenaam verwijderd (voorheen %1$s) - U heeft de uitnodiging van %1$s ingetrokken - U heeft %1$s verbannen - U heeft de verbanning van %1$s opgeheven - U heeft %1$s eruit getrapt - U sloot zich aan + Je hebt het onderwerp gewijzigd naar: %1$s + Je hebt je weergavenaam verwijderd (voorheen %1$s) + Je hebt de uitnodiging van %1$s ingetrokken + Je hebt %1$s verbannen + Je hebt de verbanning van %1$s opgeheven + Je hebt %1$s verwijderd + Je sloot je aan %1$s sluit aan - U heeft de kamer betreden - Druk op uw opname om te stoppen of om te luisteren + Je hebt de kamer betreden + Druk op je opname om te stoppen of om te luisteren Houd ingedrukt om op te nemen, laat los om te versturen Verwijder opname Stembericht aan het opnemen Pauzeer stembericht Speel stembericht af - Iedereen in %s kan de ruimte vinden en betreden - het is niet nodig om iedereen handmatig uit te nodigen. U kunt dit op elk moment aanpassen in de kamer instellingen. + Iedereen in %s kan de ruimte vinden en betreden - het is niet nodig om iedereen handmatig uit te nodigen. Je kan dit op elk moment aanpassen in de kamer instellingen. Stembericht (%1$s) Kan niet antwoorden of aanpassen als stembericht actief is Kan stembericht niet opnemen Kan stembericht niet afspelen - U heeft gasten de toegang tot dit gesprek verleend. - U heeft het hoofdadres voor dit gesprek ingesteld op %1$s. - U heeft %1$s als gespreksadres toegevoegd en %2$s verwijderd. + Je hebt gasten de toegang tot dit gesprek verleend. + Je hebt het hoofdadres voor dit gesprek ingesteld op %1$s. + Je hebt %1$s als gespreksadres toegevoegd en %2$s verwijderd. - U heeft %1$s als gespreksadres verwijderd. - U heeft %1$s als gespreksadressen verwijderd. + Je hebt %1$s als gespreksadres verwijderd. + Je hebt %1$s als gespreksadressen verwijderd. - U heeft %1$s als kameradres toegevoegd. - U heeft %1$s als kameradressen toegevoegd. + Je hebt %1$s als kameradres toegevoegd. + Je hebt %1$s als kameradressen toegevoegd. - U heeft de uitnodiging van %1$s ingetrokken. Reden: %2$s - U heeft de uitnodiging voor %1$s aanvaard. Reden: %2$s + Je hebt de uitnodiging van %1$s ingetrokken. Reden: %2$s + Je hebt de uitnodiging voor %1$s aanvaard. Reden: %2$s 🎉 Alle servers zijn uitgesloten van deelname! Deze kamer kan niet meer gebruikt worden. • Servers die overeenkomen met IP-tekens zijn nu verbannen. • Servers die overeenkomen met IP-tekens zijn nu toegestaan. • Servers die overeenkomen met IP-letters zijn verbannen. • Servers die overeenkomen met IP-tekens zijn toegestaan. %s heeft de server ACL\'s voor deze kamer ingesteld. - U heeft de server ACL\'s voor deze kamer ingesteld. - U heeft de server ACL\'s voor deze kamer aangepast. + Je hebt de server ACL\'s voor deze kamer ingesteld. + Je hebt de server ACL\'s voor deze kamer aangepast. %s heeft de server ACL\'s voor deze kamer aangepast. • Servers die overeenkomen met %s zijn verwijderd uit de toegestane lijst. • Servers die overeenkomen met %s zijn nu toegestaan. @@ -911,9 +911,9 @@ • Servers die overeenkomen met %s zijn nu verbannen. • Servers die overeenkomen met %s zijn toegestaan. • Servers die overeenkomen met %s zijn verbannen. - U heeft hier geüpgraded. + Je hebt hier geüpgraded. %s heeft hier geüpgraded. - U heeft toekomstige kamergeschiedenis zichtbaar gemaakt voor %1$s + Je hebt toekomstige kamergeschiedenis zichtbaar gemaakt voor %1$s %1$ds over %s is toegetreden. Conclusie Bevestiging @@ -958,7 +958,7 @@ Klaar! Berichtsleutel Herstelwachtwoordzin - Bevestiging Geannuleerd + Verificatie geannuleerd Sleutelverzoeken Verwijderen Bevestigen Accountgegevens @@ -988,7 +988,7 @@ %s heeft geannuleerd Jij hebt geaccepteerd %s heeft geaccepteerd - U heeft geannuleerd + Je hebt geannuleerd Niet beveiligd Ze komen overeen Versleuteling inschakelen @@ -1055,13 +1055,13 @@ Overige Geen Persoon negeren - Uzelf degraderen\? + Jezelf degraderen\? Uitnodiging annuleren In de wacht zetten SSL-fout. Camera wisselen Draadloze Koptelefoon - Ruimten + Spaces Wisselen Opwaarderen Aanbevolen @@ -1169,10 +1169,10 @@ Wachten… Formaat: Url: - sessie_naam: - app_weergave_naam: - push_key: - app_id: + Sessie weergavenaam: + App weergavenaam: + Push key: + App ID: Voorkeuren Algemeen BEKIJKEN @@ -1186,36 +1186,36 @@ Succes Kopiëren Geef toestemming om de camera te gebruiken via de systeeminstellingen om deze actie uit te voeren. - Sommige rechten ontbreken om deze actie uit te voeren, geeft a.u.b. toestemming via de systeeminstellingen. + Sommige rechten ontbreken om deze actie uit te voeren, geeft toestemming via de systeeminstellingen. Ruimten Begin met chatten Herstellen Afwijzen Systeemstandaard - U heeft end-to-end-versleuteling ingeschakeld (onbekend algoritme %1$s). - U heeft end-to-end-versleuteling ingeschakeld. - U heeft gasten de toegang tot het gesprek verhinderd. + Je hebt eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %1$s). + Je hebt eind-tot-eind-versleuteling ingeschakeld. + Je hebt gasten de toegang tot het gesprek verhinderd. %1$s heeft gasten de toegang tot het gesprek verhinderd. - U heeft gasten de toegang tot het gesprek verhinderd. - U heeft hier gasten toegelaten. + Je hebt gasten de toegang tot het gesprek verhinderd. + Je hebt hier gasten toegelaten. %1$s heeft hier gasten toegelaten. - U heeft het gespreksadres gewijzigd. + Je hebt het gespreksadres gewijzigd. %1$s heeft het gespreksadres gewijzigd. - U heeft het hoofdadres en alternatieve gespreksadres gewijzigd. + Je hebt het hoofdadres en alternatieve gespreksadres gewijzigd. %1$s heeft het hoofdadres en alternatieve gespreksadres gewijzigd. - U heeft het alternatieve gespreksadres gewijzigd. + Je hebt het alternatieve gespreksadres gewijzigd. %1$s heeft het alternatieve gespreksadres gewijzigd. - U heeft alternatief gespreksadres %1$s verwijderd. - U heeft alternatieve gespreksadressen %1$s verwijderd. + Je hebt alternatief gespreksadres %1$s verwijderd. + Je hebt alternatieve gespreksadressen %1$s verwijderd. %1$s heeft %2$s als alternatief gespreksadres verwijderd. %1$s heeft %2$s als alternatieve gespreksadressen verwijderd. - U heeft %1$s als alternatief gespreksadres toegevoegd. - U heeft %1$s als alternatieve gespreksadressen toegevoegd. + Je hebt %1$s als alternatief gespreksadres toegevoegd. + Je hebt %1$s als alternatieve gespreksadressen toegevoegd. %1$s heeft %2$s als alternatief gespreksadres toegevoegd. @@ -1224,31 +1224,31 @@ Aan de slag Spacerechten Gespreksrechten - Door de verbanning op te heffen kan deze gebruiker opnieuw deelnemer worden van de ruimte. - Door de verbanning op te heffen kan deze gebruiker opnieuw deelnemer worden van de kamer. - Door deze persoon te verbannen zal hij/zij verwijderd worden uit deze space en voorkomen dat hij/zij opnieuw toetreedt. + Door de verbanning op te heffen kan deze persoon opnieuw deelnemer worden van de space. + Door de verbanning op te heffen kan deze persoon opnieuw deelnemer worden van de kamer. + Door deze persoon te verbannen zullen ze verwijderd worden uit deze space en voorkomen dat ze opnieuw toetreden. Reden voor verbanning - De gebruiker zal worden verwijderd uit deze ruimte. + De persoon zal worden verwijderd uit deze space. \n -\nOm te voorkomen dat ze opnieuw toetreden, kunt u ze verbannen. - Door deze persoon te verwijderen zal hij/zij niet meer in dit gesprek zitten. +\nOm te voorkomen dat ze opnieuw toetreden, kan je ze verbannen. + De persoon zal worden verwijderd van deze kamer. \n -\nOm te voorkomen dat hij/zij opnieuw toetreedt, kun je hem/haar ook verbannen. +\nOm te voorkomen dat ze opnieuw toetreden, kan je ze verbannen. Reden voor verwijdering - Weet u zeker dat u uitnodiging voor deze persoon wilt annuleren\? - Als u deze persoon niet negeert, worden alle berichten van deze persoon opnieuw weergegeven. + Weet je zeker dat je de uitnodiging voor deze persoon wilt annuleren\? + Als je deze persoon niet negeert, worden alle berichten van deze persoon opnieuw weergegeven. Door deze persoon te negeren worden zijn/haar berichten verwijderd uit gesprekken die jullie delen. \n -\nU kunt deze actie op elk moment ongedaan maken in de algemene instellingen. - U kunt deze wijziging niet ongedaan maken omdat uzelf degradeert, als u de laatste persoon met rechten bent in het gesprek zal het onmogelijk zijn om opnieuw rechten te krijgen. - Deze kamer is niet publiek. U kunt niet opnieuw deelnemer worden zonder uitnodiging. - Toegang verlenen tot uw contactpersonen. - Om de QR-code te scannen moet u toegang verlenen tot de camera. +\nJe kan deze actie op elk moment ongedaan maken in de algemene instellingen. + Je kan deze wijziging niet ongedaan maken omdat je jezelf degradeert, als je de laatste persoon met rechten bent in het gesprek zal het onmogelijk zijn om opnieuw rechten te krijgen. + Deze kamer is niet publiek. Je kan niet opnieuw deelnemer worden zonder uitnodiging. + Toegang verlenen tot je contactpersonen. + Om de QR-code te scannen moet je toegang verlenen tot de camera. Oproep beëindigen… Geen antwoord - De persoon die u heeft gebeld is bezet. + De persoon die je hebt gebeld is bezet. Persoon bezet - U heeft de oproep in de wacht gezet + Je hebt de oproep in de wacht gezet %s heeft de oproep in de wacht gezet Bellen met %s Videobellen met %s @@ -1272,7 +1272,7 @@ HD uitschakelen Geluidsapparaat Selecteren Kan geen realtime verbinding tot stand brengen. -\nVraag de beheerder van uw server om een TURN-server te configureren om gesprekken betrouwbaar te laten werken. +\nVraag de beheerder van jouw server om een TURN-server te configureren om gesprekken betrouwbaar te laten werken. ${app_name} Oproep Mislukt Server API URL Sleutel deelverzoekgeschiedenis versturen @@ -1284,36 +1284,36 @@ Nieuwe waarde Widget verwijderen mislukt Widget toevoegen mislukt - U kunt uzelf niet bellen, wacht totdat deelnemers de uitnodiging accepteren - U kunt niet met uzelf bellen - Vergaderingen gebruiken beveiligings- en toestemmingsbeleid van Jitsi. Alle huidige personen in het gesprek zullen een uitnodiging zien terwijl uw vergadering bezig is. + Je kunt jezelf niet bellen, wacht totdat deelnemers de uitnodiging accepteren + Je kunt niet met jezelf bellen + Vergaderingen gebruiken beveiligings- en toestemmingsbeleid van Jitsi. Alle huidige personen in het gesprek zullen een uitnodiging zien terwijl je vergadering bezig is. Geluidsvergadering starten Videoconferentie starten - U mist de rechten om een oproep te starten - U mist de rechten om een oproep in dit gesprek te starten - U mist de rechten om een vergadering te starten - U mist de rechten om een vergadering in dit gesprek te starten + Je mist de rechten om een oproep te starten + Je mist de rechten om een oproep in dit gesprek te starten + Je mist de rechten om een vergadering te starten + Je mist de rechten om een vergadering in dit gesprek te starten Ontbrekende rechten Geef toestemming om de microfoon te gebruiken om stemberichten te versturen. Alles herstellen - U bent toegetreden. + Je bent toegetreden. Er is een verificatie e-mail verzonden naar %1$s. Controleer je inbox Dit e-mailadres is niet aan een account gekoppeld - Als u uw wachtwoord wijzigt, worden alle end-to-end-versleutelingssleutels voor al uw sessies opnieuw ingesteld, waardoor de gecodeerde chatgeschiedenis onleesbaar wordt. Stel een back-up sleutel in of exporteer uw kamersleutels uit een andere sessie voordat u uw wachtwoord opnieuw instelt. - Er wordt een verificatie-e-mail naar uw inbox gestuurd om het instellen van uw nieuwe wachtwoord te bevestigen. + Als je jouw wachtwoord wijzigt, worden alle eind-tot-eind-versleutelingssleutels voor al je sessies opnieuw ingesteld, waardoor de gecodeerde chatgeschiedenis onleesbaar wordt. Stel een back-up sleutel in of exporteer je kamersleutels uit een andere sessie voordat je jouw wachtwoord opnieuw instelt. + Er wordt een verificatie-e-mail naar jouw inbox gestuurd om het instellen van je nieuwe wachtwoord te bevestigen. Wachtwoord opnieuw instellen op %1$s Dit e-mailadres is niet gekoppeld aan een account. De applicatie kan geen account aanmaken op deze server. \n -\nWilt u zich aanmelden met een webclient\? +\nWil je jezelf aanmelden met een webclient\? Sorry, deze server accepteert geen nieuwe accounts. De applicatie kan niet inloggen op deze server. De thuisserver ondersteunt de volgende aanmeldingstype(s): %1$s. \n \nWil je inloggen met een webclient\? Er is een fout opgetreden bij het laden van de pagina: %1$s (%2$d) - Voer het adres in van de server die u wilt gebruiken - Voer het adres in van de Modular Element of de server die u wilt gebruiken + Voer het adres in van de server die je wil gebruiken + Voer het adres in van de Modular Element of de server die je wil gebruiken Premium hosting voor organisaties Element Matrix Services-adres Geschiedenis wissen @@ -1331,21 +1331,21 @@ Word gratis lid met miljoenen anderen op de grootste openbare server Net als e-mail hebben accounts één thuis, hoewel je met iedereen kunt praten Selecteer een server - Breid en pas uw ervaring aan + Breid uit en personaliseer je ervaring Houd gesprekken privé met versleuteling Chat direct met mensen of in groepen - Het is jouw gesprek. Bezet het. - U heeft deze op enkel uitnodiging gemaakt. + Het is jouw gesprek. Bezit het. + Je hebt deze op enkel uitnodiging gemaakt. %1$s heeft dit alleen op uitnodiging gemaakt. Je hebt de kamer alleen op uitnodiging gemaakt. %1$s heeft de kamer alleen voor uitnodigingen ingesteld. - U heeft de kamer openbaar gemaakt voor iedereen die de link kent. - U negeert geen enkele persoon + Je heb de kamer openbaar gemaakt voor iedereen die de link kent. + Je negeert geen enkele persoon %1$s heeft de kamer openbaar gemaakt voor iedereen die de link kent. Klik lang op een kamer om meer opties te zien Schrijf trefwoorden om een reactie te vinden. Stuurt het gegeven bericht als een spoiler - U heeft geen wijzigingen aangebracht + Je hebt geen wijzigingen aangebracht %1$s heeft geen wijzigingen aangebracht Kamer instellingen Verlaat de kamer @@ -1356,15 +1356,15 @@ Alle belangrijke berichten Deze inhoud is als ongepast gerapporteerd. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen. Gemeld als ongepast Deze inhoud is gerapporteerd als spam. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen. Gerapporteerd als spam Deze inhoud is gemeld. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen. Reden voor het rapporteren van deze inhoud Deze inhoud rapporteren Er zijn geen bestanden in deze kamer @@ -1394,49 +1394,49 @@ Open het menu kamer maken Open de navigatielade Het lijkt erop dat de server er te lang over doet om te reageren. Dit kan worden veroorzaakt door een slechte verbinding of een fout met de server. Probeer het over een tijdje opnieuw. - Probeer het opnieuw zodra u de algemene voorwaarden van uw homeserver hebt geaccepteerd. - Uitgebreide logboeken helpen ontwikkelaars door meer logboeken te verstrekken wanneer u een RageShake verzendt. Zelfs wanneer ingeschakeld, registreert de toepassing geen berichtinhoud of andere privégegevens. + Probeer het opnieuw zodra je de algemene voorwaarden van je homeserver hebt geaccepteerd. + Uitgebreide logboeken helpen ontwikkelaars door meer logboeken te verstrekken wanneer je een RageShake verzendt. Zelfs wanneer ingeschakeld, registreert de toepassing geen berichtinhoud of andere privégegevens. Uitgebreide logboeken inschakelen - Ga akkoord met de servicevoorwaarden van de identiteitsserver (%s), zodat u vindbaar bent op e-mailadres of telefoonnummer. - U deelt momenteel e-mailadressen of telefoonnummers op de identiteitsserver %1$s. U moet opnieuw verbinding maken met %2$s om ze niet meer te delen. + Ga akkoord met de servicevoorwaarden van de identiteitsserver (%s), zodat je vindbaar bent op e-mailadres of telefoonnummer. + Je deelt momenteel e-mailadressen of telefoonnummers op de identiteitsserver %1$s. Je moet opnieuw verbinding maken met %2$s om ze niet meer te delen. De verificatiecode is niet correct. Er is een sms-bericht verzonden naar %s. Voer de verificatiecode in die deze bevat. - De door u gekozen identiteitsserver heeft geen servicevoorwaarden. Ga alleen verder als je de eigenaar van de service vertrouwt + De door jouw gekozen identiteitsserver heeft geen servicevoorwaarden. Ga alleen verder als je de eigenaar van de service vertrouwt Identiteitsserver heeft geen servicevoorwaarden Voer de URL van de identiteitsserver in Kan geen verbinding maken met identiteitsserver Voer een identiteitsserver URL in - Gaat u akkoord met het versturen van deze informatie\? - Om bestaande contacten te ontdekken, moet u contactgegevens (e-mailadressen en telefoonnummers) naar uw identiteitsserver sturen. We hashen uw gegevens voordat ze worden verzonden vanwege privacy. + Ga je akkoord met het versturen van deze informatie\? + Om bestaande contacten te ontdekken, moet je contactgegevens (e-mailadressen en telefoonnummers) naar je identiteitsserver sturen. We hashen je gegevens voordat ze worden verzonden vanwege privacy. Stuur e-mailadressen en telefoonnummers naar %s Toestemming geven Mijn toestemming intrekken - Uw thuisserverbeleid - Kan geen server bereiken op de URL %s. Controleer uw link of kies handmatig een server. - Uw contacten zijn privé. Om personen van uw contacten te ontdekken, hebben we uw toestemming nodig om contactgegevens naar uw identiteitsserver te sturen. - We hebben u een bevestigingsmail gestuurd naar %s, controleer eerst uw e-mail en klik op de bevestigingslink - We hebben u een bevestigingsmail gestuurd naar %s, controleer uw e-mail en klik op de bevestigingslink - Ontdekkingsopties verschijnen zodra u een e-mail heeft toegevoegd. - U gebruikt momenteel %1$s om te ontdekken en vindbaar te zijn voor bestaande contacten die u kent. - U bekijkt deze kamer al! - Er kan geen voorbeeld van deze kamer worden bekeken. Wilt u deelnemen\? + Jouw thuisserverbeleid + Kan geen server bereiken op de URL %s. Controleer je link of kies handmatig een server. + Jouw contacten zijn privé. Om personen van je contacten te ontdekken, hebben we jouw toestemming nodig om contactgegevens naar je identiteitsserver te sturen. + We hebben een e-mail gestuurd naar %s, controleer eerst je e-mail en klik op de bevestigingslink + We hebben een e-mail gestuurd naar %s, controleer je e-mail en klik op de bevestigingslink + Ontdekkingsopties verschijnen zodra je een e-mailadres hebt toegevoegd. + Je gebruikt momenteel %1$s om te ontdekken en vindbaar te zijn voor bestaande contacten die je kent. + Je bekijkt deze kamer al! + Er kan geen voorbeeld van deze kamer worden bekeken. Wil je toetreden\? Deze kamer is op dit moment niet toegankelijk. -\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of u toegang heeft. - Verander uw avatar alleen in deze huidige kamer - Verander uw schermnaam alleen in de huidige kamer - Andere spaces of kamers die u misschien niet kent - Space die u kent die deze kamer bevat - Stel adressen in voor deze kamer zodat personen deze kamer kunnen vinden via uw server (%1$s) - U kunt dit op elk moment uitschakelen in de instellingen - U krijgt geen meldingen voor vermeldingen en trefwoorden in versleutelde kamers op uw mobiel. - Zorg ervoor dat u op de link heeft geklikt in de e-mail die we u hebben gestuurd. - U hebt uw toestemming gegeven om e-mails en telefoonnummers naar deze identiteitsserver te sturen om andere personen van uw contacten te ontdekken. +\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of je toegang hebt.
+ Verander je afbeelding alleen in deze kamer + Verander je weergavenaam alleen in de huidige kamer + Andere spaces of kamers die je misschien niet kent + Space die je kent die deze kamer bevat + Stel adressen in voor deze kamer zodat personen deze kamer kunnen vinden via jouw server (%1$s) + Je kan dit op elk moment uitschakelen in de instellingen + Je krijgt geen meldingen voor vermeldingen en trefwoorden in versleutelde kamers op je mobiel. + Zorg ervoor dat je op de link hebt geklikt in de e-mail die we je hebben gestuurd. + Je hebt toestemming gegeven om e-mails en telefoonnummers naar deze identiteitsserver te sturen om andere personen van je contacten te ontdekken. E-mailadressen en telefoonnummers versturen Vindbare telefoonnummers - Als u de verbinding met uw identiteitsserver verbreekt, betekent dit dat u niet door andere personen kan worden gevonden en dat u anderen niet per e-mail of telefoon kunt uitnodigen. - Ontdekkingsopties verschijnen zodra u een telefoonnummer heeft toegevoegd. + Als je de verbinding met je identiteitsserver verbreekt, betekent dit dat je niet door andere personen kan worden gevonden en dat je anderen niet per e-mail of telefoon kan uitnodigen. + Ontdekkingsopties verschijnen zodra je een telefoonnummer hebt toegevoegd. Vindbare e-mailadressen - U gebruikt momenteel geen identiteitsserver. Om te ontdekken en vindbaar te zijn door bestaande contacten die u kent, configureert u er een hieronder. + Je gebruikt momenteel geen identiteitsserver. Om te ontdekken en vindbaar te zijn door bestaande contacten die Je kent, configureer je er een hieronder. Geen beleid geleverd door de identiteitsserver Identiteitsserverbeleid verbergen Identiteitsserverbeleid weergeven @@ -1460,7 +1460,7 @@ Bekijk de kamer directory Een nieuw privébericht versturen Nieuwe kamer aanmaken - Kunt u niet vinden wat u zoekt\? + Kan je niet vinden wat je zoekt\? Geen bewerkingen gevonden Bestand %1$s is gedownload! Video comprimeren %d%% @@ -1471,14 +1471,14 @@ Verborgen gebeurtenissen op de tijdlijn weergeven Geef feedback De feedback kan niet worden verzonden (%s) - Bedankt, uw feedback is succesvol verzonden - U kunt contact met mij opnemen als u vervolgvragen heeft - U gebruikt een bètaversie van spaces. Uw feedback zal helpen bij het informeren van de volgende versies. Uw platform en inlognaam worden genoteerd om ons te helpen uw feedback zoveel mogelijk te gebruiken. + Bedankt, je feedback is succesvol verzonden + Je kan contact met mij opnemen als je vervolgvragen hebt + Je gebruikt een bètaversie van spaces. Jouw feedback zal helpen bij het informeren van de volgende versies. Jouw platform en inlognaam worden genoteerd om ons te helpen jouw feedback zoveel mogelijk te gebruiken. Spaces feedback De suggestie kan niet worden verzonden (%s) Bedankt, de suggestie is succesvol verzonden - Beschrijf hier uw suggestie - Schrijf hieronder uw suggestie. + Beschrijf hier jouw suggestie + Schrijf hieronder jouw suggestie. Een voorstel doen Systeeminstellingen Versies @@ -1500,7 +1500,7 @@ \n \n%s
Kameronderwerp (optioneel) - Nieuwe ruimte aanmaken + Nieuwe space aanmaken Geeft een plaatsvervangende melding weer voor verwijderde berichten. Verwijderde berichten weergeven Beveiligde back-up instellen @@ -1519,9 +1519,9 @@ %1$s in %2$s en %3$s Deze server is al aanwezig in de lijst Kan deze server of de kamerlijst niet vinden - Voer de naam in van een nieuwe server die u wilt verkennen. + Voer de naam in van een nieuwe server die je wil verkennen. Een nieuwe server toevoegen - Uw server + Jouw server Sleutel %1$d/%2$d geïmporteerd met succes. %1$d/%2$d sleutels met succes geïmporteerd. @@ -1562,7 +1562,7 @@ Een nieuw adres handmatig publiceren Andere gepubliceerde adressen: Dit is het hoofdadres - Gepubliceerde adressen kunnen door iedereen op elke server worden gebruikt om lid te worden van uw kamer. Om een adres te publiceren, moet het eerst als lokaal adres worden ingesteld. + Gepubliceerde adressen kunnen door iedereen op elke server worden gebruikt om lid te worden van jouw kamer. Om een adres te publiceren, moet het eerst als lokaal adres worden ingesteld. Gepubliceerde adressen Adressen van deze kamer bekijken en beheren. Ruimte-adressen @@ -1574,27 +1574,27 @@ Wie heeft toegang\? Wijzigingen in wie geschiedenis kan lezen, zijn alleen van toepassing op toekomstige berichten in deze kamer. De zichtbaarheid van de bestaande historie blijft ongewijzigd. Account instellingen - U kunt meldingen beheren in %1$s. + Je kan meldingen beheren in %1$s. Houd er rekening mee dat vermeldingen en trefwoordmeldingen niet beschikbaar zijn in versleutelde kamers op mobiel. Informeer mij voor - Beheer e-mailadressen en telefoonnummers die aan uw Matrix-account zijn gekoppeld + Beheer e-mailadressen en telefoonnummers die aan je Matrix-account zijn gekoppeld E-mailadressen en telefoonnummers Schakel hiervoor \'Integraties toestaan\' in bij Instellingen. Integraties zijn uitgeschakeld Deze server biedt geen beleid. Bibliotheken van derden - Uw identiteitsserverbeleid + Jouw identiteitsserverbeleid ${app_name}-beleid We delen geen informatie met derden We registreren of profileren geen accountgegevens hier - Help ons problemen te identificeren en ${app_name} te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door uw apparaten wordt gedeeld. + Help ons problemen te identificeren en ${app_name} te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door jouw apparaten wordt gedeeld. \n -\nU kunt al onze voorwaarden %s lezen. +\nJe kan al onze voorwaarden %s lezen. Help ${app_name} verbeteren - Dit zal uw huidige sleutel of zin vervangen. - Genereer een nieuwe beveiligingssleutel of stel een nieuwe beveiligingszin in voor uw bestaande back-up. - Bescherm uzelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op uw server. + Dit zal jouw huidige sleutel of zin vervangen. + Genereer een nieuwe beveiligingssleutel of stel een nieuwe beveiligingszin in voor je bestaande back-up. + Bescherm jezelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op je server. Instellen op dit apparaat Beveiligde back-up resetten Beveiligde back-up instellen @@ -1616,23 +1616,23 @@ Versleutelde berichten in groepsgesprekken Versleutelde berichten in één-op-één gesprekken Er is op de melding geklikt! - Klik op de melding. Als u de melding niet ziet, controleer dan de systeeminstellingen. - U bekijkt de melding! Klik hier! + Klik op de melding. Als je de melding niet ziet, controleer dan de systeeminstellingen. + Je bekijkt de melding! Klik hier! Kan push niet ontvangen. Oplossing zou kunnen zijn om de applicatie opnieuw te installeren. De applicatie ontvangt PUSH De applicatie wacht op de PUSH Trefwoorden mogen \'%s\' niet bevatten Trefwoorden mogen niet beginnen met \'.\' Nieuw trefwoord toevoegen - Uw trefwoorden + Jouw trefwoorden Breng me op de hoogte voor Vermeldingen en trefwoorden Standaardmeldingen E-mailmeldingen inschakelen voor %s - Om e-mail met melding te ontvangen, koppelt u een e-mail aan uw Matrix-account + Om e-mail met melding te ontvangen, koppel je een e-mailadres aan je Matrix-account E-mail notificatie - Er is geen e-mailadres toegevoegd aan uw account - Er is geen telefoonnummer toegevoegd aan uw account + Er is geen e-mailadres toegevoegd aan je account + Er is geen telefoonnummer toegevoegd aan je account De sessie is afgemeld! De kamer is verlaten! Alleen vermeldingen en trefwoorden @@ -1660,8 +1660,8 @@ Personen uitnodigen Berichten sturen Standaardrol - U bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van deze space te wijzigen - U bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van de kamer te wijzigen + Je bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van deze space te wijzigen + Je bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van de kamer te wijzigen Selecteer de rollen die nodig zijn om verschillende delen van deze space te wijzigen Selecteer de rollen die nodig zijn om verschillende delen van de kamer te veranderen Bekijk en update de rollen die nodig zijn om verschillende delen van de kamer te veranderen. @@ -1669,8 +1669,8 @@ Kies server Niet nu Inschakelen - Luisteren naar notificaties - U mag niet deelnemen aan deze kamer + Luisteren naar meldingen + Je mag niet toetreden tot deze kamer Gebeurtenis status verzonden! Gebeurtenis verzonden! Misvormde gebeurtenis @@ -1706,7 +1706,7 @@ Sleutel importeren uit bestand Widgets openen Authenticatie mislukt - ${app_name} vereist dat u uw inloggegevens invoert om deze actie uit te voeren. + ${app_name} vereist dat je jouw inloggegevens invoert om deze actie uit te voeren. Opnieuw authenticatie nodig Schuif om het gesprek te beëindigen Onbekend persoon @@ -1737,20 +1737,20 @@ Terugbellen Dit gesprek is beëindigd %1$s heeft dit gesprek geweigerd - U heeft deze oproep geweigerd + Je hebt deze oproep geweigerd Veranderingen ongedaan maken Er zijn niet opgeslagen wijzigingen. De wijzigingen negeren\? De kamer is nog niet aangemaakt. Het aanmaken van een kamer annuleren\? De link was verkeerd ingedeeld QR-code niet gescand! Ongeldige QR-code (ongeldige URI)! - U kunt uzelf niet DM\'en! + Je kan jezelf niet DM\'en! Deel via tekst Kan deze kamer niet vinden. Zorg ervoor dat het bestaat. - U kunt geen kamer openen waar u uit bent verbannen. - Wijzig uw huidige pincode + Je kan geen kamer openen waar je uit bent verbannen. + Wijzig je huidige pincode Verander pincode - Elke keer dat u ${app_name} opent, is een pincode vereist. + Elke keer dat je ${app_name} opent, is een pincode vereist. Pincode is vereist na 2 minuten ${app_name} niet te hebben gebruikt. Pincode vereist na 2 minuten Geef alleen het aantal ongelezen berichten weer in een eenvoudige melding. @@ -1759,16 +1759,16 @@ Pincode is de enige manier om ${app_name} te ontgrendelen. Schakel apparaatspecifieke biometrische gegevens in, zoals vingerafdrukken en gezichtsherkenning. Biometrische gegevens inschakelen - Als u uw pincode opnieuw wilt instellen, tikt u op Pincode vergeten om uit te loggen en opnieuw in te stellen. + Als je jouw pincode opnieuw wilt instellen, tik je op Pincode vergeten om uit te loggen en opnieuw in te stellen. Pincode inschakelen Beveiliging configureren Beveilig de toegang met pincode en biometrie. Toegang beveiligen - Om uw pincode opnieuw in te stellen, moet u opnieuw inloggen en een nieuwe maken. - Voer uw pincode in + Om je pincode opnieuw in te stellen, moet je opnieuw inloggen en een nieuwe maken. + Voer je pincode in Kan pincode niet valideren. Tik voor een nieuwe. Kies een pincode voor beveiliging - Te veel fouten, u bent uitgelogd + Te veel fouten, je bent uitgelogd Waarschuwing! Laatste resterende poging voor uitloggen! %d invoer @@ -1778,49 +1778,49 @@ Verkeerde code, %d resterende poging Verkeerde code, %d resterende pogingen - Controleer uw instellingen om pushmeldingen in te schakelen + Controleer je instellingen om pushmeldingen in te schakelen Pushmeldingen zijn uitgeschakeld Kan persoon verbanning niet opheffen Verbannen door %1$s Uitnodiging voor %1$s intrekken\? Zoeken naar contacten op Matrix - Uw contactenboek is leeg - Uw contacten ophalen… + Jouw contactenboek is leeg + Jouw contacten ophalen… Herstelsleutel opslaan in LEER MEER BEGREPEN - We zijn verheugd om aan te kondigen dat we van naam zijn veranderd! Uw app is up-to-date en u bent ingelogd op uw account. + We zijn verheugd om aan te kondigen dat we van naam zijn veranderd! Jouw app is up-to-date en je bent ingelogd op jouw account. Riot is nu Element! Wachten op versleutelingsgeschiedenis - U heeft geen toegang tot dit bericht omdat de afzender de sleutels met opzet niet heeft verzonden - U heeft geen toegang tot dit bericht omdat u bent geblokkeerd door de afzender - U heeft geen toegang tot dit bericht omdat uw sessie niet wordt vertrouwd door de afzender - Vanwege end-to-end-versleuteling moet u mogelijk wachten op het bericht van iemand omdat de versleutelingssleutels niet correct naar u zijn verzonden. + Je hebt geen toegang tot dit bericht omdat de afzender de sleutels met opzet niet heeft verzonden + Je hebt geen toegang tot dit bericht omdat je bent geblokkeerd door de afzender + Je hebt geen toegang tot dit bericht omdat jouw sessie niet wordt vertrouwd door de afzender + Vanwege eind-tot-eind-versleuteling moet je mogelijk wachten op het bericht van iemand omdat de versleutelingssleutels niet correct naar jou zijn verzonden. Wachten op dit bericht, dit kan even duren - U heeft geen toegang tot dit bericht + Je hebt geen toegang tot dit bericht Avatar instellen Je hebt de kamerinstellingen met succes gewijzigd - Voer uw beveiligingszin nogmaals in om deze te bevestigen. - Voer een beveiligingszin in die alleen u kent en die wordt gebruikt om geheimen op uw server te beveiligen. + Voer jouw beveiligingszin nogmaals in om deze te bevestigen. + Voer een beveiligingszin in die alleen jij kent en die wordt gebruikt om geheimen op jouw server te beveiligen. Stel een beveiligingszin in - Bewaar uw beveiligingssleutel ergens veilig, zoals een wachtwoordbeheerder of een kluis. - Bewaar uw beveiligingssleutel - Voer een geheime zin in die alleen u kent en genereer een sleutel voor back-up. + Bewaar jouw beveiligingssleutel ergens veilig, zoals een wachtwoordbeheerder of een kluis. + Bewaar jouw beveiligingssleutel + Voer een geheime zin in die alleen jij kent en genereer een sleutel voor back-up. Gebruik een beveiligingszin Genereer een beveiligingssleutel om ergens veilig op te slaan, zoals een wachtwoordbeheerder of een kluis. Een beveiligingssleutel gebruiken - Bescherm uzelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op uw server. + Bescherm jezelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op je server. Start de camera Stop de camera Dempen van de microfoon opheffen De microfoon dempen Voer de URL van een identiteitsserver in - U kunt ook een andere identiteitsserver URL invoeren - Uw server (%1$s) stelt voor om %2$s te gebruiken voor uw identiteitsserver + Je kan ook een andere identiteitsserver URL invoeren + Je server (%1$s) stelt voor om %2$s te gebruiken voor jouw identiteitsserver De toestemming van de persoon is niet gegeven. Er is geen huidige associatie met dit id. De associatie heeft gefaald. - Voor uw privacy ondersteunt ${app_name} alleen het versturen van gehashte e-mailadressen en telefoonnummers van personen. + Voor je privacy ondersteunt ${app_name} alleen het versturen van gehashte e-mailadressen en telefoonnummers van personen. Accepteer eerst de voorwaarden van de identiteitsserver in de instellingen. Configureer eerst een identiteitsserver. Deze operatie is niet mogelijk. De server is verouderd. @@ -1829,11 +1829,11 @@ Open voorwaarden van %s Beschikbare talen laden… Andere beschikbare talen - Deel deze code met mensen zodat ze deze kunnen scannen om u toe te voegen en te beginnen met chatten. + Deel deze code met mensen zodat ze deze kunnen scannen om je toe te voegen en te beginnen met chatten. Mijn code Mijn code delen Een QR-code scannen - We kunnen geen personen uitnodigen. Controleer de personen die u wilt uitnodigen en probeer het opnieuw. + We kunnen geen personen uitnodigen. Controleer de personen die je wil uitnodigen en probeer het opnieuw. Uitnodigingen verzonden naar %1$s en nog één Uitnodigingen verzonden naar %1$s en %2$d meer @@ -1845,19 +1845,19 @@ Hé, praat met me op ${app_name}: %s Vrienden uitnodigen Mensen toevoegen - We kunnen je DM niet maken. Controleer de personen die u wilt uitnodigen en probeer het opnieuw. - De link %1$s brengt u naar een andere site: %2$s. + We kunnen je DM niet maken. Controleer de personen die je wilt uitnodigen en probeer het opnieuw. + De link %1$s brengt je naar een andere site: %2$s. \n -\nWeet u zeker dat u door wilt gaan\? +\nWeet je zeker dat je door wilt gaan\? Dubbelcheck deze link Kies een wachtwoord. Kies een inlognaam. Kan kruislingsondertekenen niet instellen - Bevestig uw identiteit door deze login te verifiëren en deze toegang te verlenen tot versleutelde berichten. - Bevestig uw identiteit door deze login van een van uw andere sessies te verifiëren en toegang te verlenen tot versleutelde berichten. + Bevestig je identiteit door deze login te verifiëren en deze toegang te verlenen tot versleutelde berichten. + Bevestig je identiteit door deze login van een van uw andere sessies te verifiëren en toegang te verlenen tot versleutelde berichten. Interactief verifiëren door Emoji Handmatig verifiëren via tekst - Verifieer de nieuwe login voor toegang tot uw account: %1$s + Verifieer de nieuwe login voor toegang tot je account: %1$s Verifieer al je sessies om ervoor te zorgen dat je account en berichten veilig zijn Bekijk waar je bent ingelogd Versleuteld door een niet-geverifieerd apparaat @@ -1866,34 +1866,34 @@ Stuurt het gegeven bericht met sneeuwval Stuurt het gegeven bericht met confetti - Laat het apparaat zien waarmee u nu kunt verifiëren - %d apparaten weergeven waarmee u nu kunt verifiëren + Laat het apparaat zien waarmee je nu kan verifiëren + %d apparaten weergeven waarmee je nu kan verifiëren - U start opnieuw op zonder geschiedenis, geen berichten, vertrouwde apparaten of vertrouwde personen + Je start opnieuw op zonder geschiedenis, geen berichten, vertrouwde apparaten of vertrouwde personen Als je alles reset - Doe dit alleen als u geen ander apparaat heeft waarmee u dit apparaat kunt verifiëren. + Doe dit alleen als je geen ander apparaat hebt waarmee je dit apparaat kunt verifiëren. Alle herstelopties vergeten of verloren\? Alles resetten Kan geen toegang krijgen tot beveiligde opslag - Selecteer uw herstelsleutel of voer deze handmatig in door deze te typen of te plakken vanaf uw klembord + Selecteer je herstelsleutel of voer deze handmatig in door deze te typen of te plakken vanaf je klembord Herstelsleutel gebruiken - Gebruik uw %1$s of gebruik uw %2$s om door te gaan. + Gebruik je %1$s of gebruik je %2$s om door te gaan. Alleen ondersteund in versleutelde kamers Dwingt dat de huidige uitgaande groepssessie in een versleutelde kamer wordt weggegooid - Gebruik de nieuwste ${app_name} op uw andere apparaten: + Gebruik de nieuwste ${app_name} op je andere apparaten: of een andere Matrix client die kruislingsondetekenen ondersteunt ${app_name} iOS \n${app_name} Android ${app_name} Web \n${app_name} Desktop - Gebruik de nieuwste ${app_name} op uw andere apparaten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} voor Android of een andere Matrix-client die geschikt is voor kruislingsondertekenen + Gebruik de nieuwste ${app_name} op je andere apparaten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} voor Android of een andere Matrix-client die geschikt is voor kruislings ondertekenen Stel een nieuw accountwachtwoord in… Kan mediabestand niet opslaan - Als u deze instelling inschakelt, wordt de FLAG_SECURE aan alle activiteiten toegevoegd. Start de toepassing opnieuw om de wijziging door te voeren. + Als je deze instelling inschakelt, wordt de FLAG_SECURE aan alle activiteiten toegevoegd. Start de applicatie opnieuw om de wijziging door te voeren. Voorkom screenshots van de applicatie Sleutel Back-up herstelsleutel - Weet u uw Key Back-up wachtwoordzin niet, u kunt %s. - gebruik uw Backup-herstelsleutel - Voer uw Sleutel Back-up wachtwoordzin in om door te gaan. + Weet je jouw Key Back-up wachtwoordzin niet, je kan %s. + gebruik je Backup-herstelsleutel + Voer je Sleutel Back-up wachtwoordzin in om door te gaan. Sleutelback-up geheim opslaan in SSSS SSSS sleutel genereren uit herstelsleutel SSSS sleutel genereren op basis van wachtwoordzin (%s) @@ -1903,8 +1903,8 @@ Back-upsleutel controleren Voer een herstelsleutel in Het is geen geldige herstelsleutel - Voer uw %s in om door te gaan - Verifieer uzelf en anderen om uw chats veilig te houden + Voer je %s in om door te gaan + Verifieer jezelf en anderen om jouw chats veilig te houden Encryptie upgrade beschikbaar Dit account is gedeactiveerd. Onjuiste inlognaam en/of wachtwoord. Het ingevoerde wachtwoord begint of eindigt met spaties, controleer dit alstublieft. @@ -1915,24 +1915,24 @@ Bijna klaar! Toont het andere apparaat een vinkje\? Een onderwerp toevoegen %s om mensen te laten weten waar deze kamer over gaat. - Dit is het begin van uw privéberichtgeschiedenis met %s. + Dit is het begin van jouw privéberichtgeschiedenis met %s. Dit is het begin van dit gesprek. Dit is het begin van %s. - U hebt de kamer gemaakt en geconfigureerd. + Je hebt de kamer gemaakt en geconfigureerd. %s heeft de kamer gemaakt en geconfigureerd. De versleuteling die door deze kamer wordt gebruikt, wordt niet ondersteund Versleuteling niet ingeschakeld - Berichten in deze chat zijn end-to-end-versleuteld. + Berichten in deze chat zijn eind-tot-eind-versleuteld. Berichten in deze kamer zijn eind-tot-eind-versleuteld. Lees meer en verifieer persoon in hun profiel. - Als u nu annuleert, kunt u versleutelde berichten en gegevens kwijtraken als u de toegang tot uw aanmeldingen verliest. + Als je nu annuleert, kan je versleutelde berichten en gegevens kwijtraken als je de toegang tot uw aanmeldingen verliest. \n -\nU kunt ook Veilige back-up instellen en uw sleutels beheren in Instellingen. - Kopieer het naar uw persoonlijke cloudopslag +\nJe kan ook Veilige back-up instellen en uw sleutels beheren in Instellingen. + Kopieer het naar je persoonlijke cloudopslag Bewaar het op een USB-stick of back-upstation Print het uit en bewaar het ergens veilig - Uw %2$s en %1$s zijn nu ingesteld. + Jouw %2$s en %1$s zijn nu ingesteld. \n -\nHoud ze veilig! U heeft ze nodig om versleutelde berichten te ontgrendelen en informatie te beveiligen als u al uw actieve sessies verliest. +\nHoud ze veilig! Je hebt ze nodig om versleutelde berichten te ontgrendelen en informatie te beveiligen als je al jouw actieve sessies verliest. Sleutelback-up instellen Zelfondertekenende sleutel synchroniseren Persoonssleutel synchroniseren @@ -1943,25 +1943,25 @@ Hou het veilig Herstel instellen. Dit kan enkele seconden duren, even geduld a.u.b. - Voer een beveiligingszin in die alleen u kent en die wordt gebruikt om geheimen op uw server te beveiligen. - Gebruik niet uw accountwachtwoord. - Voer uw %s in om door te gaan. - Verificatie is geannuleerd. U kunt de verificatie opnieuw starten. + Voer een beveiligingszin in die alleen jij kent en die wordt gebruikt om geheimen op jouw server te beveiligen. + Gebruik niet je accountwachtwoord. + Voer je %s in om door te gaan. + Verificatie is geannuleerd. Je kan de verificatie opnieuw starten. Een van de volgende zaken kan worden aangetast: \n -\n- Uw wachtwoord -\n- Uw server +\n- Jouw wachtwoord +\n- Jouw server \n- Dit apparaat, of het andere apparaat \n- De internetverbinding die elk apparaat gebruikt \n -\nWe raden u aan uw wachtwoord en herstelsleutel onmiddellijk in Instellingen te wijzigen. - U verifieert %1$s (%2$s) niet als u nu annuleert. Begin opnieuw in hun profiel. - Als u annuleert, kunt u geen versleutelde berichten op dit apparaat lezen en zullen andere personen het niet vertrouwen - Als u annuleert, kunt u geen versleutelde berichten lezen op je nieuwe apparaat en zullen andere personen het niet vertrouwen - Uw account is mogelijk gecompromitteerd +\nWe raden je aan jouw wachtwoord en herstelsleutel onmiddellijk in Instellingen te wijzigen.
+ Je verifieert %1$s (%2$s) niet als je nu annuleert. Begin opnieuw in hun profiel. + Als je annuleert, kan je geen versleutelde berichten op dit apparaat lezen en zullen andere personen het niet vertrouwen + Als je annuleert, kan je geen versleutelde berichten lezen op je nieuwe apparaat en zullen andere personen het niet vertrouwen + Jouw account is mogelijk gecompromitteerd Dit was ik niet - Gebruik deze sessie om uw nieuwe te verifiëren en deze toegang te verlenen tot versleutelde berichten. - Nieuwe login. Was u dit\? + Gebruik deze sessie om je nieuwe te verifiëren en deze toegang te verlenen tot versleutelde berichten. + Nieuwe login. Was jij dit\? Ontgrendel de geschiedenis van versleutelde berichten Exportcontrole ${app_name} Android @@ -1970,7 +1970,7 @@ Gebeurtenis verwijderd door persoon, reden: %1$s Reden voor redigeren Geef een reden op - Weet u zeker dat u deze gebeurtenis wilt verwijderen (wissen)\? Houd er rekening mee dat als u een kamer naam of onderwerpwijziging verwijdert, de wijziging ongedaan kan worden gemaakt. + Weet je zeker dat je deze gebeurtenis wil verwijderen (wissen)\? Houd er rekening mee dat als je een kamer naam of onderwerpwijziging verwijdert, de wijziging ongedaan kan worden gemaakt. Media versturen in het originele formaat Stuur video in het originele formaat @@ -1980,7 +1980,7 @@ Stuur afbeelding in het originele formaat Stuur afbeeldingen in het originele formaat - Wilt u deze bijlage naar %1$s sturen\? + Wil je deze bijlage naar %1$s sturen\? Kan geen geheimen vinden in opslag Als je geen toegang hebt tot een bestaande sessie Een herstelwachtwoordzin of -sleutel gebruiken @@ -1990,8 +1990,8 @@ Vliegtuigmodus is ingeschakeld Verbinding met de server is verbroken Bijna klaar! Toont %s een vinkje\? - Totdat deze persoon deze sessie vertrouwt, worden berichten die van en naar de sessie worden verzonden, gelabeld met waarschuwingen. U kunt het ook handmatig verifiëren. - %1$s (%2$s) aangemeld met een nieuwe sessie: + Totdat deze persoon deze sessie vertrouwt, worden berichten die van en naar deze sessie worden verzonden, gelabeld met waarschuwingen. Je kan het ook handmatig verifiëren. + %1$s (%2$s) is aangemeld met een nieuwe sessie: Deze sessie wordt vertrouwd voor veilig berichtenverkeer omdat %1$s (%2$s) deze heeft geverifieerd: Kan geen sessies ophalen Gebruik een bestaande sessie om deze te verifiëren en deze toegang te verlenen tot versleutelde berichten. @@ -2000,39 +2000,39 @@ %d actieve sessie %d actieve sessies - Verifieer deze sessie om hem als vertrouwd te markeren en verleen hem toegang tot versleutelde berichten. Als u zich niet bij deze sessie hebt aangemeld, is uw account mogelijk gehackt: - Deze sessie wordt vertrouwd voor veilige berichten omdat u deze heeft geverifieerd: + Verifieer deze sessie om als vertrouwd te markeren en toegang te verlenen tot versleutelde berichten. Als jij je niet bij deze sessie hebt aangemeld, is jouw account mogelijk gehackt: + Deze sessie wordt vertrouwd voor veilige berichten omdat je deze hebt geverifieerd: Geen cryptografische informatie beschikbaar Standaardversie Kamerversies 👓 De limiet is onbekend. - Uw server accepteert bijlagen (bestanden, media, enz.) met een grootte tot %s. + Jouw server accepteert bijlagen (bestanden, media, enz.) met een grootte tot %s. Server limiet voor het uploaden van bestanden Serverversie Server naam Afmelden voor deze sessie Alle sessies tonen - Uw serverbeheerder heeft standaard end-to-end versleuteling uitgeschakeld in privékamers en privéberichten. + Jouw serverbeheerder heeft standaard eind-tot-eind versleuteling uitgeschakeld in privékamers en privéberichten. Kruisondertekenen is niet ingeschakeld - Kruisondertekenen is ingeschakeld. + Kruislings ondertekenen is ingeschakeld. \nSleutels worden niet vertrouwd - Kruisondertekenen is ingeschakeld + Kruislings ondertekenen is ingeschakeld \nSleutels zijn vertrouwd. \nPrivésleutels zijn niet bekend Kruisondertekening is ingeschakeld \nPrivésleutels op het apparaat. - Uw nieuwe sessie is nu geverifieerd. Het heeft toegang tot uw gecodeerde berichten en andere personen zullen het als vertrouwd zien. - Berichten met deze persoon zijn end-to-end-versleuteld en kunnen niet door derden worden gelezen. + Jouw nieuwe sessie is nu geverifieerd. Het heeft toegang tot jouw gecodeerde berichten en andere personen zullen het als vertrouwd zien. + Berichten met deze persoon zijn eind-tot-eind-versleuteld en kunnen niet door derden worden gelezen. Vergelijk de code met die op het scherm van de andere persoon. Vergelijk de unieke emoji en zorg ervoor dat ze in dezelfde volgorde verschijnen. Doe dit voor de zekerheid persoonlijk of gebruik een andere manier om te communiceren. - Om veilig te zijn, verifieert u %s door een eenmalige code te controleren. + Om veilig te zijn, verifieer je %s door een eenmalige code te controleren. Eenmaal ingeschakeld, kan versleuteling voor een kamer niet worden uitgeschakeld. Berichten die in een versleutelde kamer worden verzonden, kunnen niet door de server worden gezien, alleen door de deelnemers van de kamer. Het inschakelen van versleuteling kan voorkomen dat veel bots en koppelingen correct werken. - U bent niet gemachtigd om versleuteling in deze kamer in te schakelen. - End-to-end-versleuteling inschakelen… + Je bent niet gemachtigd om versleuteling in deze kamer in te schakelen. + Eind-tot-eind-versleuteling inschakelen… Verzendt de gegeven emote gekleurd als een regenboog Stuurt het gegeven bericht gekleurd als een regenboog - Deze sessie kan deze verificatie niet delen met uw andere sessies. + Deze sessie kan deze verificatie niet delen met jouw andere sessies. \nDe verificatie wordt lokaal opgeslagen en gedeeld in een toekomstige versie van de app. ${app_name} heeft een probleem ondervonden bij het weergeven van de inhoud van het gebeurtenis met id \'%1$s\' ${app_name} verwerkt geen gebeurtenissen van het type \'%1$s\' @@ -2043,40 +2043,40 @@ Moderator in %1$s Beheerder in %1$s De kamer verlaten… - Berichten hier zijn end-to-end-versleuteld. + Berichten hier zijn eind-tot-eind-versleuteld. \n -\nUw berichten zijn beveiligd met sloten en alleen u en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. - Berichten in deze kamer zijn end-to-end-versleuteld. +\nJouw berichten zijn beveiligd met sloten en alleen jij en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. + Berichten in deze kamer zijn eind-tot-eind-versleuteld. \n -\nUw berichten zijn beveiligd met sloten en alleen u en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. - Berichten hier zijn niet end-to-end-versleuteld. - Berichten in deze kamer zijn niet end-to-end-versleuteld. +\nJouw berichten zijn beveiligd met sloten en alleen jij en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. + Berichten hier zijn niet eind-tot-eind-versleuteld. + Berichten in deze kamer zijn niet eind-tot-eind-versleuteld. Wachten op %s… Verifieer door emoji\'s te vergelijken Verifieer door emoji te vergelijken - Als u niet persoonlijk aanwezig bent, vergelijk dan emoji\'s + Als je niet persoonlijk aanwezig bent, vergelijk dan emoji\'s Scannen met dit apparaat Scan hun code - Scan de code met uw ander apparaat of wissel en scan met dit apparaat + Scan de code met je andere apparaat of wissel en scan met dit apparaat Scan de code met het apparaat van de andere persoon om elkaar veilig te verifiëren Deze sessie verifiëren Gereageerd met: %s - "Een van de volgende zaken kan worden aangetast: + Een van de volgende zaken kan worden aangetast: \n -\n - Uw server -\n - De server waarmee de gebruiker die u verifieert is verbonden -\n - De internetverbinding van u of de andere personen -\n - Het apparaat van u of van andere personen" +\n - Jouw server +\n - De server waarmee de gebruiker die je verifieert is verbonden +\n - De internetverbinding van jou of de andere personen +\n - Het apparaat van jou of van andere personen Ze komen niet overeen Niet-vertrouwd inloggen - Uw e-maildomein is niet geautoriseerd om op deze server te registreren + Jouw e-maildomein is niet geautoriseerd om op deze server te registreren Ruimte aanmaken… Kamer aanmaken… Sommige tekens zijn niet toegestaan Geef een kameradres op Dit adres is al in gebruik Ruimte-adres - U kunt dit inschakelen als de kamer alleen wordt gebruikt voor samenwerking met interne teams op uw server. Dit kan later niet meer worden gewijzigd. + Je kan dit inschakelen als de kamer alleen wordt gebruikt voor samenwerking met interne teams op jouw server. Dit kan later niet meer worden gewijzigd. Blokkeer iedereen die geen deel uitmaakt van %s om ooit deel te nemen aan deze kamer Verberg geavanceerd Geavanceerd weergeven @@ -2090,87 +2090,87 @@ Schud je telefoon om de detectiedrempel te testen De ontwikkelaarsmodus activeert verborgen functies en kan de applicatie ook minder stabiel maken. Alleen voor ontwikkelaars! De beschrijving is te kort - Uw matrix.to link is onjuist opgemaakt - De huidige sessie is voor gebruiker %1$s en u geeft inloggegevens op voor persoon %2$s. Dit wordt niet ondersteund door ${app_name}. -\nWis eerst de gegevens en meld u vervolgens opnieuw aan met een ander account. - U raakt de toegang tot beveiligde berichten kwijt, tenzij u zich aanmeldt om uw versleutelingssleutels te herstellen. + Jouw matrix.to link is onjuist opgemaakt + De huidige sessie is voor gebruiker %1$s en je geeft inloggegevens op voor persoon %2$s. Dit wordt niet ondersteund door ${app_name}. +\nWis eerst de gegevens en meld je vervolgens opnieuw aan met een ander account. + Je raakt de toegang tot beveiligde berichten kwijt, tenzij je jezelf aanmeldt om jouw versleutelingssleutels te herstellen. Alle gegevens wissen die momenteel op dit apparaat zijn opgeslagen\? -\nMeld u opnieuw aan om toegang te krijgen tot uw accountgegevens en berichten. +\nMeld je opnieuw aan om toegang te krijgen tot je accountgegevens en berichten. Alle gegevens wissen - Waarschuwing: uw persoonlijke gegevens (inclusief versleutelingssleutels) zijn nog steeds opgeslagen op dit apparaat. + Waarschuwing: jouw persoonlijke gegevens (inclusief versleutelingssleutels) zijn nog steeds opgeslagen op dit apparaat. \n -\nWis het als u klaar bent met het gebruik van dit apparaat of als u zich wilt aanmelden bij een ander account. +\nWis het als je klaar bent met het gebruik van dit apparaat of als je jezelf wilt aanmelden bij een ander account. Persoonlijke gegevens wissen Log in om versleutelingssleutels te herstellen die exclusief op dit apparaat zijn opgeslagen. Je hebt ze nodig om al uw beveiligde berichten op elk apparaat te lezen. - Uw server (%1$s) beheerder heeft u uitgelogd van uw account %2$s (%3$s). + Je server (%1$s) beheerder heeft je uitgelogd van jouw account %2$s (%3$s). Je bent uitgelogd Opnieuw inloggen Het kan verschillende redenen hebben: \n -\n• U heeft uw wachtwoord bij een andere sessie gewijzigd. +\n• Je hebt je wachtwoord bij een andere sessie gewijzigd. \n -\n• U heeft deze sessie verwijderd uit een andere sessie. +\n• Je hebt deze sessie verwijderd uit een andere sessie. \n -\n• De beheerder van uw server heeft uw toegang om veiligheidsredenen ongeldig gemaakt. +\n• De beheerder van je server heeft jouw toegang om veiligheidsredenen ongeldig gemaakt. Je bent uitgelogd - Kan geen geldige server vinden. Controleer uw ID a.u.b. + Kan geen geldige server vinden. Controleer je ID Dit is geen geldige persoon-ID. Verwacht formaat: \'@persoon:server.org\' - Als u uw wachtwoord niet weet, gaat u terug om het opnieuw in te stellen. + Als je jouw wachtwoord niet weet, ga je terug om het opnieuw in te stellen. Als je een account aanmaakt op een server, gebruik dan je Matrix ID (bijv. @persoon:domein.nl) en wachtwoord hieronder. Aanmelden met Matrix ID Aanmelden met Matrix ID - Er zijn te veel verzoeken verzonden. Je kunt het over %1$d seconde opnieuw proberen… - Er zijn te veel verzoeken verzonden. Je kunt het over %1$d seconden opnieuw proberen… + Er zijn te veel verzoeken verzonden. Je kan het over %1$d seconde opnieuw proberen… + Er zijn te veel verzoeken verzonden. Je kan het over %1$d seconden opnieuw proberen… - Deze server draait op een oude versie. Vraag uw server beheerder om te upgraden. U kunt doorgaan, maar sommige functies werken mogelijk niet correct. + Deze server draait op een oude versie. Vraag je server beheerder om te upgraden. Je kan doorgaan, maar sommige functionaliteiten werken mogelijk niet correct. De ingevoerde code is niet correct. Gelieve dit te controleren. We hebben zojuist een e-mail gestuurd naar %1$s. \nKlik op de link die deze bevat om door te gaan met het aanmaken van een account. - Controleer uw e-mail + Controleer je e-mail Accepteer de voorwaarden om door te gaan Voer de captcha uitdaging uit Selecteer een aangepaste server Selecteer Element Matrix Services - Uw account is nog niet aangemaakt. Het registratieproces stoppen\? + Jouw account is nog niet aangemaakt. Het registratieproces stoppen\? Deze inlognaam is in gebruik Inlognaam of e-mailadres - Meld u aan bij %1$s + Meld je aan bij %1$s Telefoonnummer lijkt ongeldig. Controleer het alstublieft Internationale telefoonnummers moeten beginnen met \'+\' Gebruik het internationale formaat (telefoonnummer moet beginnen met \'+\') - We hebben zojuist een code naar %1$s gestuurd. Voer het hieronder in om te verifiëren dat u het bent. + We hebben zojuist een code naar %1$s gestuurd. Voer het hieronder in om te verifiëren dat jij het bent. Telefoonnummer bevestigen Telefoon nummer (optioneel) Gebruik het internationale formaat. - Stel een telefoonnummer in om optioneel toe te staan dat mensen die u kent u kunnen ontdekken. + Stel een telefoonnummer in om optioneel toe te staan dat mensen die je kent jou kunnen ontdekken. Telefoonnummer instellen Lijkt niet op een geldig e-mailadres - Stel een e-mail in om uw account te herstellen. Later kunt u optioneel toelaten dat mensen die u kent u via uw e-mail ontdekken. + Stel een e-mail in om je account te herstellen. Later kan je optioneel toestaan dat mensen die je kent jou via je e-mail ontdekken. E-mailadres instellen - Uw wachtwoord is nog niet gewijzigd. + Jouw wachtwoord is nog niet gewijzigd. \n \nHet proces voor het wijzigen van het wachtwoord stoppen\? Terug naar Inloggen - U bent bij alle sessies uitgelogd en ontvangt geen pushmeldingen meer. Log opnieuw in op elk apparaat om meldingen weer in te schakelen. + Je bent bij alle sessies uitgelogd en ontvangt geen pushmeldingen meer. Log opnieuw in op elk apparaat om meldingen weer in te schakelen. Je wachtwoord is gereset. Ik heb mijn e-mailadres geverifieerd - Tik op de link om uw nieuwe wachtwoord te bevestigen. Klik hieronder als u de link hebt gevolgd die erin staat. - Word eigenaar van uw gesprekken. - Ruimte aanmaken - Ruimte aanmaken… + Tik op de link om je nieuwe wachtwoord te bevestigen. Klik hieronder als je de link hebt gevolgd die erin staat. + Word eigenaar van jouw gesprekken. + Space aanmaken + Space aanmaken… Een ruimte aanmaken Gebeurtenis inhoud Houd er rekening mee dat bij het upgraden een nieuwe versie van de kamer wordt gemaakt. Alle huidige berichten blijven in deze gearchiveerde kamer. - Iedereen in een ouderkamer kan deze kamer vinden en er lid van worden. Het is niet nodig om iedereen handmatig uit te nodigen. U kunt dit op elk moment wijzigen in de kamer instellingen. + Iedereen in een ouderspace kan deze kamer vinden en er lid van worden. Het is niet nodig om iedereen handmatig uit te nodigen. Je kan dit op elk moment wijzigen in de kamer instellingen. Het upgraden van een kamer is een geavanceerde actie en wordt meestal aanbevolen wanneer een kamer onstabiel is vanwege bugs, ontbrekende functies of beveiligingsproblemen. \nDit heeft meestal alleen invloed op hoe de kamer op de server wordt verwerkt. Beheer kamers - U bent de enige beheerder van deze kamer. Als u het verlaat, betekent dit dat niemand er controle over heeft. + Je bent de enige beheerder van deze kamer. Als je het verlaat, betekent dit dat niemand er controle over heeft. Deze alias is momenteel niet toegankelijk. -\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of u toegang heeft. +\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of je toegang hebt. Word lid van mijn kamer %1$s %2$s - Als u lid wilt worden van een bestaande kamer, heeft u een uitnodiging nodig. + Als je lid wil worden van een bestaande kamer, heb je een uitnodiging nodig. Weet je zeker dat je alle niet verzonden berichten in deze kamer wilt verwijderen\? Automatisch rapport versleutelingsfouten. Poll maken @@ -2179,7 +2179,7 @@ Upload bestand Afbeeldingen en video\'s versturen Open camera - Weet u zeker dat u deze poll wilt verwijderen\? U kunt het niet meer herstellen nadat het is verwijderd. + Weet je zeker dat je deze poll wilt verwijderen\? Je kan het niet meer herstellen nadat het is verwijderd. Poll verwijderen Poll beëindigd Stem uitgebracht @@ -2216,65 +2216,65 @@ Vraag of onderwerp Poll vraag of onderwerp Poll maken - Start de toepassing opnieuw om de wijziging door te voeren. + Start de applicatie opnieuw om de wijziging door te voeren. LaTeX wiskunde inschakelen %s in Instellingen om uitnodigingen rechtstreeks in ${app_name} te ontvangen. - Koppel deze e-mail aan uw account - Deze uitnodiging voor deze space is verzonden naar %s die niet is gekoppeld aan uw account - Deze uitnodiging voor deze kamer is verzonden naar %s die niet is gekoppeld aan uw account + Koppel deze e-mail aan je account + Deze uitnodiging voor deze space is verzonden naar %s die niet is gekoppeld aan jouw account + Deze uitnodiging voor deze kamer is verzonden naar %s die niet is gekoppeld aan jouw account Stop met opnemen Schuif om te annuleren Spraakbericht opnemen Sorry, er is een fout opgetreden bij het proberen deel te nemen aan: %s Upgrade naar de aanbevolen kamerversie In deze kamer wordt versie %s gebruikt, die door deze server als onstabiel is gemarkeerd. - U heeft toestemming nodig om een kamer te upgraden + Je hebt toestemming nodig om een kamer te upgraden Bovenliggende space automatisch bijwerken Personen automatisch uitnodigen - U update de kamer van %1$s naar %2$s. + Je update de kamer van %1$s naar %2$s. Upgrade privékamer Upgrade openbare kamer Upgrade vereist Even geduld, het kan even duren. Deelnemen aan vervangende kamer Naamloze kamer - Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en u een uitnodiging nodig heeft. - Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en u een uitnodiging nodig heeft. -\nU heeft geen rechten om kamers toe te voegen. + Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en je een uitnodiging nodig hebt. + Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en je een uitnodiging nodig hebt. +\nJe hebt geen rechten om kamers toe te voegen. Deze space heeft geen kamers - Neem contact op met uw server beheerder voor meer informatie + Neem contact op met jouw serverbeheerder voor meer informatie Het lijkt erop dat je server nog geen Spaces ondersteunt Experimenteel voelen\? -\nU kunt bestaande spaces aan een space toevoegen. - Alle kamers waarin u deelneemt, worden weergegeven in Home. +\nJe kan bestaande spaces aan een space toevoegen. + Alle kamers waarin je deelneemt, worden weergegeven in Home. Alle kamers op startscherm weergeven Kamers en spaces beheren Markeren als aanbevolen Markeren als niet aanbevolen Op zoek naar iemand die niet in %s zit\? - %s nodigt u uit - Uw systeem verzendt automatisch logboeken wanneer er een fout optreedt die niet kan worden ontsleuteld - U bent uitgenodigd + %s nodigt je uit + Jouw systeem verzendt automatisch logboeken wanneer er een fout optreedt die niet kan worden ontsleuteld + Je bent uitgenodigd Spaces zijn een nieuwe manier om kamers en mensen te groeperen. - Voeg een space toe aan elke space die u beheert. + Voeg een space toe aan elke space die jij beheert. Bestaande spaces toevoegen Bestaande kamers toevoegen Bestaande kamers en space toevoegen - U kunt pas weer deelnemen als u opnieuw wordt uitgenodigd. - U bent de enige persoon hier. Als u weggaat, kan niemand meer meedoen, ook u niet. - Weet u zeker dat u %s wilt verlaten\? + Je kan pas weer deelnemen als je opnieuw wordt uitgenodigd. + Je bent de enige persoon hier. Als je weggaat, kan niemand meer meedoen, ook jij niet. + Weet je zeker dat je %s wil verlaten\? Verlaat Kamers toevoegen Kamers ontdekken - %d persoon die u kent is al lid geworden - %d mensen die u kent zijn al lid geworden + %d persoon die je kent is al lid geworden + %d mensen die je kent zijn al lid geworden Ontdek (%s) Installatie voltooien Nodig uit via e-mail, vind contacten en meer… Voltooi het instellen van detectie. - U gebruikt momenteel geen identiteitsserver. Om teamgenoten uit te nodigen en door hen vindbaar te zijn, configureert u er hieronder een. + Je gebruikt momenteel geen identiteitsserver. Om teamgenoten uit te nodigen en door hen vindbaar te zijn, configureet je er hieronder een. Doe toch mee Space deelnemen Voor nu overslaan @@ -2285,37 +2285,37 @@ Deel link Uitnodigen via inlognaam of e-mailadres uitnodiging via e-mail - Het is alleen u op dit moment. %s zal nog beter zijn met anderen. + Het is alleen jij op dit moment. %s zal nog beter zijn met anderen. Uitnodigen voor %s Mensen uitnodigen - Nodig mensen uit voor uw space - Laten we voor elk van hen een kamer maken. U kunt later ook meer toevoegen, inclusief reeds bestaande. - Aan welke dingen werkt u\? - Zorg ervoor dat de juiste mensen toegang hebben tot %s bedrijf. U kunt later meer uitnodigen. - Wie zijn uw teamgenoten\? - We zullen kamers voor hen maken. U kunt later ook meer toevoegen. - Wat zijn enkele discussies die u wilt voeren in %s\? + Nodig mensen uit voor jouw space + Laten we voor elk van hen een kamer maken. Je kan er later ook meer toevoegen, inclusief reeds bestaande. + Aan welke dingen werk jij\? + Zorg ervoor dat de juiste mensen toegang hebben tot %s bedrijf. Je kan er later meer uitnodigen. + Wie zijn jouw teamgenoten\? + We zullen kamers voor hen maken. Je kan later ook meer toevoegen. + Wat zijn enkele discussies die je wil voeren in %s\? Geef het een naam om door te gaan. - Voeg wat details toe om mensen te helpen het te identificeren. U kunt deze op elk moment wijzigen. - Voeg wat details toe om het te laten opvallen. U kunt deze op elk moment wijzigen. - Alleen op uitnodiging, het beste voor uzelf of teams + Voeg wat details toe om mensen te helpen het te identificeren. Je kan deze op elk moment wijzigen. + Voeg wat details toe om het te laten opvallen. Je kan deze op elk moment wijzigen. + Alleen op uitnodiging, het beste voor jezelf of teams Open voor iedereen, het beste voor gemeenschappen - Een privé-ruimte voor u en uw teamgenoten + Een privé-ruimte voor jou en jouw teamgenoten Ik en teamgenoten Een privé space om je kamers te organiseren Alleen ik Zorg ervoor dat de juiste mensen toegang hebben tot %s. - Met wie werkt u samen\? - U kunt dit later wijzigen - Wat voor soort ruimte wilt u aanmaken\? - Uw privé-ruimte - Uw openbare ruimte + Met wie werk je samen\? + Je kan dit later wijzigen + Wat voor soort ruimte wil je aanmaken\? + Jouw privé-ruimte + Jouw openbare ruimte Space toevoegen Privé space Openbare space Niet verzonden berichten verwijderen Berichten kunnen niet worden verzonden - Wilt u het versturen van een bericht annuleren\? + Wil je het versturen van een bericht annuleren\? Alle mislukte berichten verwijderen Upgrade een kamer naar een nieuwe versie Verlaat kamer met gegeven id (of huidige kamer indien leeg) @@ -2325,11 +2325,11 @@ Kleur weergavenaam overschrijven Ik heb al een account Veilig berichtenverkeer. - U heeft de controle. + Jij hebt de controle. Deel locatie Open met - ${app_name} kan geen toegang krijgen tot uw locatie. Probeer het later opnieuw. - ${app_name} heeft geen toegang tot uw locatie + ${app_name} kan geen toegang krijgen tot jouw locatie. Probeer het later opnieuw. + ${app_name} heeft geen toegang tot jouw locatie Locatie Deel locatie Resultaten worden pas onthuld als je de poll beëindigt @@ -2345,36 +2345,36 @@ Versleuteling is verkeerd geconfigureerd. Hun locatie gedeeld Account aanmaken - Berichten voor uw team. - End-to-end versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling. - Kies waar je gesprekken worden bewaard, zodat u controle en onafhankelijkheid heeft. Verbonden via Matrix. - Veilige en onafhankelijke communicatie die u dezelfde mate van privacy geeft als een persoonlijk gesprek in uw eigen huis. + Berichten voor jouw team. + Eind-tot-eind versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling. + Kies waar je gesprekken worden bewaard, zodat je controle en onafhankelijkheid hebt. Verbonden via Matrix. + Veilige en onafhankelijke communicatie die je dezelfde mate van privacy geeft als een persoonlijk gesprek in je eigen huis. Locatie - De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt versturen. Klik om instellingen te openen. - De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt versturen. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen. + De versleuteling is verkeerd geconfigureerd, zodat je geen berichten kunt versturen. Klik om instellingen te openen. + De versleuteling is verkeerd geconfigureerd, zodat je geen berichten kunt versturen. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen. Berichtbubbels weergeven Kan kaart niet laden Kaart Let op: app wordt opnieuw gestart Discussieberichten inschakelen Verbinding maken met server - Wilt u lid worden van een bestaande server\? + Wil je lid worden van een bestaande server\? Sla deze vraag over Nog niet zeker\? %s Gemeenschappen Teams Vrienden en familie - We helpen u om verbinding te maken - Met wie gaat u het meest chatten\? - U bekijkt deze discussie al! + We helpen je om verbinding te maken + Met wie ga je het meest chatten\? + Je bekijkt deze thread al! Bekijk in kamer - Reageren in discussie + Reageren in thread Het commando \"%s\" wordt herkend maar niet ondersteund in discussies. - Van een discussie + Van een thread Tip: Tik lang op een bericht en gebruik \"%s\". Discussies helpen je gesprekken on-topic te houden en gemakkelijk bij te houden. - Houd discussies georganiseerd met discussielijnen - Toont alle discussies waaraan u heeft deelgenomen + Houd discussies georganiseerd met threads + Toont alle discussies waaraan je hebt deelgenomen Mijn discussies Toont alle discussies van de huidige kamer Alle discussies @@ -2384,7 +2384,7 @@ Discussies in de kamer filteren Kopieer link naar discussie Bekijk in kamer - Discussies bekijken + Thead bekijken Personen De server accepteert geen inlognaam met alleen cijfers. Kamer notificatie @@ -2408,24 +2408,24 @@ Pin van geselecteerde locatie op kaart Sla deze stap over Opslaan en doorgaan - U kunt op elk moment bij de instellingen uw profiel bijwerken + Je kan op elk moment bij de instellingen jouw profiel bijwerken Ziet er goed uit! Laten we beginnen - Tijd om een gezicht bij uw naam te voegen + Tijd om een gezicht bij je naam te voegen Voeg een profielfoto toe - U kunt dit later wijzigen + Je kunt dit later wijzigen Weergavenaam Kies een weergavenaam - Uw account %s is aangemaakt + Jouw account %s is aangemaakt Gefeliciteerd! Breng me naar het begin Personaliseer profiel - We komen dichter bij het uitbrengen van een openbare bèta voor Discussies. + We komen dichter bij het uitbrengen van een openbare bèta voor Threads. \n \nTerwijl we ons erop voorbereiden, moeten we enkele wijzigingen aanbrengen: discussies die vóór dit punt zijn gemaakt, worden weergegeven als gewone antwoorden. \n -\nDit zal een eenmalige overgang zijn, aangezien Discussies nu deel uitmaken van de Matrix-specificatie. - Discussies die bèta naderen 🎉 +\nDit zal een eenmalige overgang zijn, aangezien Threads nu deel uitmaken van de Matrix-specificatie. + Threads benaderen bèta 🎉 %1$s, %2$s en anderen %1$s en %2$s Uitzetten @@ -2437,7 +2437,7 @@ 8 uur 1 uur 15 minuten - Deel uw live locatie voor + Deel jouw live locatie voor (%1$s) %1$s (%2$s) %1$s kan niet worden afgeluisterd @@ -2448,17 +2448,17 @@ Hun live locatie gedeeld ${app_name} is ook geweldig voor op de werkplek. Het wordt vertrouwd door \'s werelds veiligste organisaties. BÈTA - Discussies zijn werk in uitvoering met nieuwe, opwindende aankomende functies, zoals verbeterde meldingen. We horen graag uw feedback! + Threads zijn werk in uitvoering met nieuwe, opwindende aankomende functies, zoals verbeterde meldingen. We horen graag jouw feedback! Discussies Beta-feedback Geef feedback BÈTA - Indien ingeschakeld, verschijnt u altijd offline voor andere personen, zelfs wanneer u de applicatie gebruikt. + Indien ingeschakeld, verschijn je altijd offline voor andere personen, zelfs wanneer je de applicatie gebruikt. Offline modus Aanwezigheid - Uw server ondersteunt momenteel geen discussies, dus deze functie kan onbetrouwbaar zijn. Sommige berichten in een discussie zijn mogelijk niet betrouwbaar beschikbaar. %sWilt u toch discussies inschakelen\? - Discussies bèta - Discussies helpen uw gesprekken on-topic te houden en gemakkelijk bij te houden. %s Als u discussies inschakelt, wordt de app vernieuwd. Bij sommige accounts kan dit langer duren. - Discussies bèta + Jouw server ondersteunt momenteel geen threads, dus deze functionaliteit kan onbetrouwbaar zijn. Sommige berichten in een thread zijn mogelijk niet betrouwbaar beschikbaar. %sWil je threads toch inschakelen\? + Threads bèta + Threads helpen je gesprekken on-topic te houden en gemakkelijk bij te houden. %s Als je threads inschakelt, wordt de app vernieuwd. Bij sommige accounts kan dit langer duren. + Threads bèta Leer meer Probeer het uit Scherm delen is bezig @@ -2482,7 +2482,7 @@ Live tot %1$s Bekijk live locatie Live locatie beëindigd - Sommige resultaten zijn mogelijk verborgen omdat ze privé zijn en u hiervoor een uitnodiging nodig heeft. + Sommige resultaten zijn mogelijk verborgen omdat ze privé zijn en je hiervoor een uitnodiging nodig hebt. Geen resultaten gevonden Verlaat geen Verlaat alles @@ -2493,7 +2493,7 @@ min u Locatie delen inschakelen - Let op: dit is een labfunctie met een tijdelijke implementatie. Dit betekent dat u uw locatiegeschiedenis niet kunt verwijderen en dat geavanceerde gebruikers uw locatiegeschiedenis kunnen zien, zelfs nadat u stopt met het delen van uw live locatie met deze ruimte. + Let op: dit is een labfunctie met een tijdelijke implementatie. Dit betekent dat je jouw locatiegeschiedenis niet kunt verwijderen en dat geavanceerde gebruikers jouw locatiegeschiedenis kunnen zien, zelfs nadat je stopt met het delen van je live locatie met deze ruimte. Live locatie delen Huidige gateway: %s Gateway @@ -2512,9 +2512,9 @@ Meldingsmethode Achtergrondsynchronisatie Google Services - Kies hoe u meldingen wilt ontvangen + Kies hoe je meldingen wil ontvangen Kan biometrische authenticatie niet inschakelen. - Biometrische authenticatie is uitgeschakeld omdat er onlangs een nieuwe biometrische authenticatiemethode is toegevoegd. U kunt het weer inschakelen in Instellingen. + Biometrische authenticatie is uitgeschakeld omdat er onlangs een nieuwe biometrische authenticatiemethode is toegevoegd. Je kan het weer inschakelen in Instellingen. Meldingsmethode resetten Profieltag: Kan eindpunt token niet registreren op server: @@ -2525,7 +2525,7 @@ Resultaten zijn zichtbaar wanneer de poll is afgelopen Bij het uitnodigen in een versleutelde ruimte die geschiedenis deelt, is de versleutelde geschiedenis zichtbaar. MSC3061: Kamersleutels delen voor eerdere berichten - Stuur uw eerste bericht om %s uit te nodigen om te chatten + Stuur je eerste bericht om %s uit te nodigen om te chatten Berichten in deze chat worden eind-tot-eind versleuteld. Ga @@ -2533,49 +2533,49 @@ %d berichten verwijderd Deel locatie - U moet de juiste rechten hebben om de live locatie in deze kamer te delen. - U heeft geen toestemming om de live locatie te delen + Je moet de juiste rechten hebben om de live locatie in deze kamer te delen. + Je hebt geen toestemming om de live locatie te delen Kan deze link niet openen: communities zijn vervangen door spaces Gebruikersnaam / E-mailadres / Telefoonnummer - Bent u een mens\? + Ben je een mens\? Volg de instructies die naar %s zijn verstuurd Wachtwoord reset Wachtwoord vergeten - Email opnieuw verzenden + E-mail opnieuw verzenden Geen e-mail ontvangen\? Volg de instructies die naar %s zijn gestuurd - Verifieer uw e-mailadres + Verifieer je e-mailadres Code nogmaals versturen Er is een code verzonden naar %s - Bevestig uw telefoonnummer + Bevestig je telefoonnummer Alle apparaten uitloggen Reset wachtwoord Zorg ervoor dat het 8 tekens of meer zijn. Kies een nieuw wachtwoord Nieuw wachtwoord - Controleer uw e-mail. - %s stuurt u een verificatielink + Controleer je e-mail. + %s stuurt je een verificatielink Bevestigingscode Telefoonnummer - %s moet uw account verifiëren - Vul uw telefoonnummer in + %s moet je account verifiëren + Vul je telefoonnummer in E-mail - %s moet uw account verifiëren - Vul uw e-mailadres in + %s moet jouw account verifiëren + Vul jouw e-mailadres in Lees de voorwaarden en het beleid van %s door Serverbeleiden Neem contact op Element Matrix Services (EMS) is een robuuste en betrouwbare hostingservice voor snelle, veilige en realtime communicatie. Ontdek hoe op element.io/ems - Wilt u uw eigen server hosten\? + Wil je jouw eigen server hosten\? Server URL - Wat is het adres van uw server\? Dit is als uw huis voor al uw data - Selecteer uw server + Wat is het adres van jouw server\? Dit is als een huis voor al jouw data + Selecteer je server Welkom terug! Bewerk Of - Waar uw gesprekken zijn opgeslagen + Waar jouw gesprekken worden opgeslagen Moet 8 tekens of meer zijn - Anderen kunnen u ontdekken %s + Anderen kunnen je ontdekken %s Maak een account aan Systeemstandaard gebruiken Handmatig kiezen @@ -2585,11 +2585,11 @@ Snelkoppelingen voor Element Oproep machtigingen inschakelen Live locatie Deze QR-code lijkt misvormd. Probeer te verifiëren met een andere methode. - U hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset uw Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. + Je hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset je Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. Kan dit apparaat niet verifiëren - Wat is het adres van uw server\? - Waar uw conversaties leven - Uw gegevens bijwerken… + Wat is het adres van jouw server\? + Waar jouw gesprekken zijn opgeslagen + Jouw gegevens bijwerken… %1$s en %2$d andere %1$s en %2$d andere @@ -2599,7 +2599,7 @@ Kan kaart niet laden \nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven. Open instellingen - Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt. + Voor de beste beveiliging verifieer je jouw sessies en meld je jezelf af bij elke sessie die je niet meer herkent of gebruikt. Andere sessies Sessies Lijst met publieke spaces @@ -2618,18 +2618,18 @@ Kamer creëren Gesprek starten Alle gesprekken - U kunt feedback geven via het menu rechtsboven. - Krijg sneller en gemakkelijker toegang tot uw ruimten (rechtsonder). - Om ${app_name} te versimpelen zijn tabbladen nu optioneel. U kunt ze beheren in het menu rechtsboven. - Hier zullen uw ongelezen berichten verschijnen wanneer u deze heeft. + Je kan feedback geven via het menu rechtsboven. + Krijg sneller en gemakkelijker toegang tot jouw spaces (rechtsonder). + Om ${app_name} te versimpelen zijn tabbladen nu optioneel. Je kan ze beheren in het menu rechtsboven. + Hier zullen jouw ongelezen berichten verschijnen wanneer je deze hebt. De allesomvattende beveiligde chat-app voor teams, vrienden en organisaties. Maak een gesprek aan of word deelnemer van een bestaande kamer om te beginnen. - Ruimten zijn een nieuwe manier om kamers en personen te groeperen. Voeg een bestaande kamer toe, of maak een nieuwe aan via de knop rechtsonder. + Spaces zijn een nieuwe manier om kamers en personen te groeperen. Voeg een bestaande kamer toe, of maak een nieuwe aan via de knop rechtsonder. - Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke u niet meer gebruikt. - Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke u niet meer gebruikt. + Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke je niet meer gebruikt. + Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke je niet meer gebruikt. Verifieer of log uit van ongeverifieerde sessies. - Verbeter uw accountbeveiliging door deze aanbevelingen te volgen. + Verbeter je accountbeveiliging door deze aanbevelingen te volgen. Al %1$d+ dag inactief (%2$s) Al %1$d+ dagen inactief (%2$s) @@ -2653,8 +2653,8 @@ Sessie verifiëren Sorry, deze kamer kon niet worden gevonden. \nProbeer het later opnieuw. %s - Dit is waar uw nieuwe verzoeken en uitnodigingen zullen verschijnen. - Ruimten zijn een nieuwe manier om kamers en personen te groeperen. Maak een ruimte aan om te beginnen. + Dit is waar jouw nieuwe verzoeken en uitnodigingen zullen verschijnen. + Spaces zijn een nieuwe manier om kamers en personen te groeperen. Maak een space aan om te beginnen. Ongeverifieerde sessie Geverifieerde sessie Onbekend apparaattype @@ -2663,8 +2663,102 @@ Mobiel Niets nieuws. Uitnodigingen - Nog geen ruimten. + Nog geen spaces. %s subitems inklappen %s subitems uitvouwen - Ruimte aanpassen - + Space veranderen + Voeg (╯°□°)╯︵ ┻━┻ toe voor elk platte tekst bericht + Spraakuitzending + ${app_name} heeft toestemming nodig om notificaties te laten zien. +\nGeef alsjeblieft toestemming. + Probeer de rich-text-editor (platte tekst-modus binnenkort beschikbaar) + Rich-text-editor inschakelen + Uitgestelde privéberichten inschakelen + Een vereenvoudigde Element met optionele tabs + Nieuwe layout inschakelen + Bevestigen + Opnieuw proberen + Je wordt ingelogd + Verbinden met apparaat + Scan QR-code + Inloggen op een mobiel apparaat\? + Toon QR-code op dit apparaat + Begin op het inlogscherm + Begin op het inlogscherm + Ga naar Instellingen -> Veiligheid & privacy + Open de app op je andere apparaat + Het inloggen is afgebroken op het andere apparaat. + Die QR-code is ongeldig. + Het andere apparaat moet ingelogd zijn. + Het andere apparaat is al ingelogd. + De aanvraag is mislukt. + De aanvraag is op het andere apparaat geweigerd. + De verbinding kon niet in de benodigde tijd tot stand worden gebracht. + Verbinden met dit apparaat wordt niet ondersteund. + Verbinding mislukt + Beveiligde verbinding tot stand gebracht + Scan de onderstaande QR-code met je uitgelogde apparaat. + Inloggen met QR-code + Gebruik de camera op dit apparaat om de op het andere apparaat getoonde QR-code te scannen: + Scan QR-code + Geverifieerde sessies + Niet-geverifieerde sessies + Inactieve sessies zijn sessies die je al een tijd niet gebruikt hebt, maar deze blijven encryptiesleutels ontvangen. +\n +\nVerwijder inactieve sessies om de veiligheid en prestaties te verbeteren. Het helpt je ook met het herkennen van mogelijk verdachte nieuwe sessies. + Inactieve sessies + Je kan dit apparaat gebruiken om in te loggen op een ander apparaat met een QR-code. Er zijn twee manier om dit te doen: + Log in met QR-code + Wees bewust dat sessienamen ook zichtbaar zijn voor personen met wie je communiceert. + Sessienaam + IP-adres + Besturingssysteem + Versie + Naam + Applicatie + Sessienaam + Ontvang pushnotificaties op deze sessie. + Pushnotificaties + Sessiedetails + Uitloggen voor deze sessie + Filter wissen + Geen inactieve sessies gevonden. + Geen niet-geverifieerde sessies gevonden. + Geen geverifieerde sessies gevonden. + Inactief + Niet geverifieerd + Geverifieerd + + %1$d dag of langer inactief + %1$d dagen of langer inactief + + Inactief + Niet klaar voor veilige communicatie + Niet geverifieerd + Klaar voor veilige communicatie + Geverifieerd + Alle sessies + Apparaat + Sessie + Huidige Sessie + Niet geverifeerd · Jouw huidige sessie + Verifieer je huidige sessie voor verbeterde veilige communicatie. + Deze sessie is klaar voor veilige communicatie. + Je huidige sessie is klaar voor veilige communicatie. + Onbekende verificatiestatus + Bufferen + Live + De authenticiteit van dit versleutelde bericht kan niet worden gegarandeerd op dit apparaat. + Incognito toetsenbord + Scan QR-code + Ingeschakeld: + Sessie ID: + Er is iets fout gegaan. Controleer je netwerkverbinding en probeer het opnieuw. + ⚠ Er zijn niet-geverifieerde apparaten in deze kamer. Deze zullen niet in staat zijn de door jouw verzonden berichten te ontsleutelen. + Stuur nooit versleutelde berichten naar niet-geverifieerde sessie in deze kamer. + Toestemming geven + ${app_name} heeft toestemming nodig om notificaties te laten zien. Notificaties kunnen je berichten, uitnodigen, etc. tonen. +\n +\nGeeft toestemming bij de volgende pop-ups om notificaties te kunnen zien. + Begrepen + \ No newline at end of file From 2a0b1ab97b2d0e969c1f1064cd8bc130236c134f Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Wed, 26 Oct 2022 05:08:57 +0000 Subject: [PATCH 0392/1068] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2519 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- library/ui-strings/src/main/res/values-pt-rBR/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index efee012faa..913dacd8bb 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2814,4 +2814,11 @@ A requisição falhou. Seja capaz de gravar e enviar broadcast de voz em timeline de sala. Broadcast de voz (sob desenvolvimento ativo) + Buffering + Pausar broadcast de voz + Tocar ou retomar broadcast de voz + Parar gravação de broadcast de voz + Pausar gravação de broadcast de voz + Retomar gravação de broadcast de voz + Ao vivo \ No newline at end of file From ae30f29782fae3f31d4efd5b5306367b6ec7ba93 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 25 Oct 2022 13:25:57 +0000 Subject: [PATCH 0393/1068] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2519 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- library/ui-strings/src/main/res/values-uk/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 941e2cc0e1..ac659aca41 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2922,4 +2922,11 @@ Запит не виконаний. Можливість записувати та надсилати голосові повідомлення до стрічки кімнати. Увімкнути голосові повідомлення (в активній розробці) + Буферизація + Призупинити голосове повідомлення + Відтворити або поновити відтворення голосового повідомлення + Припинити запис голосового повідомлення + Призупинити запис голосового повідомлення + Відновити запис голосового повідомлення + Наживо \ No newline at end of file From 8c72a76c06ad37fa16e4491d9079f9149edf6c28 Mon Sep 17 00:00:00 2001 From: phardyle Date: Thu, 27 Oct 2022 03:38:05 +0000 Subject: [PATCH 0394/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.5% (2381 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index cc2ab4b3ca..7291650a7d 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -2482,7 +2482,7 @@ 想要加入已有的服务器? 跳过此问题 家人和朋友 - 拥有你的数据 + 拥有你的对话。 位置 Threads Beta反馈 BETA From 5d4215744af9a12b65f0608fc151cdca61b7cac8 Mon Sep 17 00:00:00 2001 From: Jingchao Feng Date: Thu, 27 Oct 2022 03:41:05 +0000 Subject: [PATCH 0395/1068] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.5% (2381 of 2519 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 7291650a7d..5ab883eff7 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -2299,7 +2299,7 @@ 覆盖显示名称颜色 检查你的电子邮件。 电子邮件 - 你已掌控你的资料。 + 一切由您掌控 BETA 分享你的实时位置 缩放到当前位置 From 77f7b41cb27717711d588b26aaa5ea6b60e58697 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Oct 2022 12:21:06 +0200 Subject: [PATCH 0396/1068] Build android-sdk-6.2.2 --- tools/jitsi/build_jisti_libs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh index 445dc5e0fe..ed06142375 100755 --- a/tools/jitsi/build_jisti_libs.sh +++ b/tools/jitsi/build_jisti_libs.sh @@ -30,7 +30,7 @@ cd jitsi-meet # Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md -git checkout android-sdk-5.0.2 +git checkout android-sdk-6.2.2 echo echo "##################################################" From a1278ee2f218b0d9bc622565fadb52fbb5c512a8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 May 2022 21:07:26 +0200 Subject: [PATCH 0397/1068] Update the recipe. --- docs/jitsi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jitsi.md b/docs/jitsi.md index 4dd06effdb..d6c93c49aa 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -93,4 +93,4 @@ url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10. - Build the project and perform the sanity tests again. -- Update the file `/CHANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android. +- Create a PR for project Element Android and add a changelog file `.misc` to notify about the library upgrade. From 4515dcdfe9226e0454f496e80589b254cfdcce65 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 Oct 2022 15:14:19 +0200 Subject: [PATCH 0398/1068] Update Jitsi and WebRtc dependencies to android-sdk-6.2.2 --- build.gradle | 4 ++-- vector/build.gradle | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 6ccb83e703..7e7da48295 100644 --- a/build.gradle +++ b/build.gradle @@ -96,9 +96,9 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-6.2.2" // Note: to test Jitsi release you can use a local file like this: - // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" + // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-6.2.2" content { groups.jitsi.regex.each { includeGroupByRegex it } groups.jitsi.group.each { includeGroup it } diff --git a/vector/build.gradle b/vector/build.gradle index 0b884f4d99..23dfa8fbc7 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -246,10 +246,10 @@ dependencies { // WebRTC // org.webrtc:google-webrtc is for development purposes only // implementation 'org.webrtc:google-webrtc:1.0.+' - implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar') - + implementation('com.facebook.react:react-native-webrtc:1.106.1-jitsi-12039821@aar') // Jitsi - api('org.jitsi.react:jitsi-meet-sdk:5.0.2') { + // Note: version is 6.2.0, but built from the tag `android-sdk-6.2.2`. + api('org.jitsi.react:jitsi-meet-sdk:6.2.0') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' From 16a5046601c85c3b5e8ebefd0ece1579ac8813ff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 May 2022 13:11:27 +0200 Subject: [PATCH 0399/1068] Add a DebugJitsiActivity for debug build only. It's quite useless... --- vector-app/src/debug/AndroidManifest.xml | 1 + .../app/features/debug/DebugMenuActivity.kt | 4 ++ .../debug/jitsi/DebugJitsiActivity.kt | 45 ++++++++++++++++++ .../debug/res/layout/activity_debug_jitsi.xml | 47 +++++++++++++++++++ .../debug/res/layout/activity_debug_menu.xml | 6 +++ 5 files changed, 103 insertions(+) create mode 100644 vector-app/src/debug/java/im/vector/app/features/debug/jitsi/DebugJitsiActivity.kt create mode 100644 vector-app/src/debug/res/layout/activity_debug_jitsi.xml diff --git a/vector-app/src/debug/AndroidManifest.xml b/vector-app/src/debug/AndroidManifest.xml index a7867f4081..be2aadbeaf 100644 --- a/vector-app/src/debug/AndroidManifest.xml +++ b/vector-app/src/debug/AndroidManifest.xml @@ -4,6 +4,7 @@ + diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index f431192efd..92c7ebffdc 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -35,6 +35,7 @@ import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.toast import im.vector.app.features.debug.analytics.DebugAnalyticsActivity import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity +import im.vector.app.features.debug.jitsi.DebugJitsiActivity import im.vector.app.features.debug.leak.DebugMemoryLeaksActivity import im.vector.app.features.debug.sas.DebugSasEmojiActivity import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity @@ -121,6 +122,9 @@ class DebugMenuActivity : VectorBaseActivity() { views.debugPermission.setOnClickListener { startActivity(Intent(this, DebugPermissionActivity::class.java)) } + views.debugJitsi.setOnClickListener { + startActivity(Intent(this, DebugJitsiActivity::class.java)) + } } private fun openPrivateSettings() { diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/jitsi/DebugJitsiActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/jitsi/DebugJitsiActivity.kt new file mode 100644 index 0000000000..5c6c5d1898 --- /dev/null +++ b/vector-app/src/debug/java/im/vector/app/features/debug/jitsi/DebugJitsiActivity.kt @@ -0,0 +1,45 @@ +/* + * 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.debug.jitsi + +import android.annotation.SuppressLint +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.application.databinding.ActivityDebugJitsiBinding +import org.jitsi.meet.sdk.JitsiMeet + +@AndroidEntryPoint +class DebugJitsiActivity : VectorBaseActivity() { + + override fun getBinding() = ActivityDebugJitsiBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + @SuppressLint("SetTextI18n") + override fun initUiAndData() { + val isCrashReportingDisabled = JitsiMeet.isCrashReportingDisabled(this) + views.status.text = "Jitsi crash reporting is disabled: $isCrashReportingDisabled" + + views.splash.setOnClickListener { + JitsiMeet.showSplashScreen(this) + } + + views.dev.setOnClickListener { + JitsiMeet.showDevOptions() + } + } +} diff --git a/vector-app/src/debug/res/layout/activity_debug_jitsi.xml b/vector-app/src/debug/res/layout/activity_debug_jitsi.xml new file mode 100644 index 0000000000..0a13594854 --- /dev/null +++ b/vector-app/src/debug/res/layout/activity_debug_jitsi.xml @@ -0,0 +1,47 @@ + + + + + + + + + +