From 5e1d3e6c8df68ac05b46802679bd0508914a32c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Dec 2022 12:09:27 +0100 Subject: [PATCH 01/92] Escape % --- tools/release/releaseScript.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index f91e11584c..2ae4bf11aa 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -263,7 +263,7 @@ else fi printf "\n================================================================================\n" -printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n" +printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%%3Amain to build the 'main' branch.\n" read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl printf "\n================================================================================\n" From b3d578d6b831972c4aa7e6dd3a7429347a0e5bd0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 15 Dec 2022 12:44:40 +0100 Subject: [PATCH 02/92] Release script: Improve creation of the release on GitHub. --- tools/release/releaseScript.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index 2ae4bf11aa..8273914c88 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -190,6 +190,9 @@ yes | towncrier build --version "v${version}" printf "\n================================================================================\n" read -p "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. Do not commit your change. Press enter when it's done." +# Get the changes to use it to create the GitHub release +changelogUrlEncoded=`git diff CHANGES.md | grep ^+ | tail -n +2 | cut -c2- | jq -sRr @uri | sed s/\(/%28/g | sed s/\)/%29/g` + printf "\n================================================================================\n" printf "Committing...\n" git commit -a -m "Changelog for version ${version}" @@ -354,10 +357,15 @@ apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk" adb -d install ${apkPath} read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done." -# TODO Get the block to copy from towncrier earlier (be may be edited by the release manager)? -read -p "Create the release on gitHub from the tag https://github.com/vector-im/element-android/tags, copy paste the block from the file CHANGES.md. Press enter when it's done." -read -p "Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}. Press enter when it's done." +printf "\n================================================================================\n" +githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}" +printf "Creating the release on gitHub.\n" +printf "Open this link: ${githubCreateReleaseLink}\n" +printf "Then\n" +printf " - click on the 'Generate releases notes' button\n" +printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n" +read -p ". Press enter when it's done. " printf "\n================================================================================\n" printf "Message for the Android internal room:\n\n" From c3ad7faa2c951d232471a52d68c870a2bb347400 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 23:02:43 +0000 Subject: [PATCH 03/92] Bump dependency-check-gradle from 7.4.1 to 7.4.3 Bumps dependency-check-gradle from 7.4.1 to 7.4.3. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle 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 0f94fc418c..7e5d659c8b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' 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.4.1' + classpath 'org.owasp:dependency-check-gradle:7.4.3' 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 5ee3eefe964a4dc77f2cae24dafe3a1088fa2332 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Jan 2023 16:55:25 +0100 Subject: [PATCH 04/92] Pull branch sooner to ensure release version is correctly guessed --- tools/release/releaseScript.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh index 8273914c88..553c02101c 100755 --- a/tools/release/releaseScript.sh +++ b/tools/release/releaseScript.sh @@ -87,6 +87,14 @@ fi printf "OK\n" +printf "\n================================================================================\n" +printf "Ensuring main and develop branches are up to date...\n" + +git checkout main +git pull +git checkout develop +git pull + printf "\n================================================================================\n" # Guessing version to propose a default version versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3` @@ -103,14 +111,6 @@ versionMinor=`echo ${version} | cut -d "." -f2` versionPatch=`echo ${version} | cut -d "." -f3` nextPatchVersion=$((versionPatch + 2)) -printf "\n================================================================================\n" -printf "Ensuring main and develop branches are up to date...\n" - -git checkout main -git pull -git checkout develop -git pull - printf "\n================================================================================\n" printf "Starting the release ${version}\n" git flow release start ${version} From 56986c3a77b77b19ea9be782543719e0bdffccf5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Jan 2023 21:15:08 +0100 Subject: [PATCH 05/92] Add a way to get the access token from the advances settings. --- library/ui-strings/src/main/res/values/strings.xml | 3 +++ .../settings/VectorSettingsAdvancedSettingsFragment.kt | 9 +++++++++ .../main/res/xml/vector_settings_advanced_settings.xml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..1aad226dcf 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3501,4 +3501,7 @@ sent a video. sent a sticker. created a poll. + + Access Token + Your access token gives full access to your account. Do not share it with anyone. diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index b6fa997f41..514f2529e9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -25,6 +25,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.core.utils.copyToClipboard import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.NightlyProxy import im.vector.app.features.rageshake.RageShake @@ -64,6 +65,14 @@ class VectorSettingsAdvancedSettingsFragment : override fun bindPref() { setupRageShakeSection() setupNightlySection() + setupDevToolsSection() + } + + private fun setupDevToolsSection() { + findPreference("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener { + copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken) + true + } } private fun setupRageShakeSection() { diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 9260b33162..6399d54cbb 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -93,6 +93,12 @@ android:title="@string/settings_key_requests" app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" /> + + Date: Tue, 3 Jan 2023 11:55:32 +0100 Subject: [PATCH 06/92] Avoid launching coroutine for nothing. --- .../sdk/internal/session/StreamEventsManager.kt | 10 ++++------ .../session/sync/handler/room/RoomSyncHandler.kt | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index cfc26045a0..ce34b0430e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -42,14 +42,12 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { + fun dispatchLiveEventReceived(event: Event, roomId: String) { Timber.v("## dispatchLiveEventReceived ${event.eventId}") coroutineScope.launch { - if (!initialSync) { - listeners.forEach { - tryOrNull { - it.onLiveEvent(roomId, event) - } + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 4001ae2ccf..d67b903bb9 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 @@ -423,7 +423,9 @@ internal class RoomSyncHandler @Inject constructor( val isInitialSync = insertType == EventInsertType.INITIAL_SYNC eventIds.add(event.eventId) - liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) + if (!isInitialSync) { + liveEventService.get().dispatchLiveEventReceived(event, roomId) + } if (event.isEncrypted() && !isInitialSync) { try { From 0e504e90145f021a7a1867184cc1a373dc5a5c2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 11:55:41 +0100 Subject: [PATCH 07/92] Format --- .../session/sync/handler/room/RoomSyncHandler.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 d67b903bb9..ccc3820bb6 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 @@ -376,8 +376,15 @@ 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, roomSync.unreadThreadNotifications, aggregator = aggregator) + roomSummaryUpdater.update( + realm, + roomId, + membership, + roomSync.summary, + roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, + aggregator = aggregator, + ) return roomEntity } From 6f384c799f46fe63c55b71a3590a8b0349861bbd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:02:45 +0100 Subject: [PATCH 08/92] Batch insertion of `shouldShareHistory` --- .../internal/crypto/DefaultCryptoService.kt | 2 ++ .../internal/crypto/store/IMXCryptoStore.kt | 3 +++ .../crypto/store/db/CryptoStoreAggregator.kt | 21 +++++++++++++++ .../crypto/store/db/RealmCryptoStore.kt | 26 ++++++++++++++++--- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7862da1c17..128f06eacb 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -384,6 +384,7 @@ internal class DefaultCryptoService @Inject constructor( } } } + cryptoStore.onSyncWillProcess() } private fun internalStart() { @@ -432,6 +433,7 @@ internal class DefaultCryptoService @Inject constructor( * @param syncResponse the syncResponse */ fun onSyncCompleted(syncResponse: SyncResponse) { + cryptoStore.onSyncCompleted() cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 21e3342365..a285dbec78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -48,6 +48,9 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { + fun onSyncWillProcess() + fun onSyncCompleted() + /** * @return the device id */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt new file mode 100644 index 0000000000..529cad6869 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.crypto.store.db + +data class CryptoStoreAggregator( + val setShouldShareHistoryData: MutableMap = mutableMapOf() +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 1b52b79746..a98a7ee0ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -716,9 +716,7 @@ internal class RealmCryptoStore @Inject constructor( override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - doRealmTransaction(realmConfiguration) { - CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory - } + cryptoStoreAggregator?.setShouldShareHistoryData?.put(roomId, shouldShareHistory) } override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { @@ -1815,4 +1813,26 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } + + private var cryptoStoreAggregator: CryptoStoreAggregator? = null + override fun onSyncWillProcess() { + if (cryptoStoreAggregator != null) { + Timber.e("cryptoStoreAggregator is not null...") + } + cryptoStoreAggregator = CryptoStoreAggregator() + } + + override fun onSyncCompleted() { + val aggregator = cryptoStoreAggregator ?: return Unit.also { + Timber.e("cryptoStoreAggregator is null...") + } + + doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> + // setShouldShareHistory + aggregator.setShouldShareHistoryData.map { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value + } + } + cryptoStoreAggregator = null + } } From c1a8bf828b9b5569a2e6498c7eec99a1d2c8597b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:15:15 +0100 Subject: [PATCH 09/92] Batch insertion of `shouldEncryptForInvitedMembers` --- .../sdk/internal/crypto/store/db/CryptoStoreAggregator.kt | 3 ++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt index 529cad6869..165062f17c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -17,5 +17,6 @@ package org.matrix.android.sdk.internal.crypto.store.db data class CryptoStoreAggregator( - val setShouldShareHistoryData: MutableMap = mutableMapOf() + val setShouldShareHistoryData: MutableMap = mutableMapOf(), + val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index a98a7ee0ec..3f9e22140e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -708,9 +708,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - doRealmTransaction(realmConfiguration) { - CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers - } + cryptoStoreAggregator?.setShouldEncryptForInvitedMembersData?.put(roomId, shouldEncryptForInvitedMembers) } override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { @@ -1832,6 +1830,10 @@ internal class RealmCryptoStore @Inject constructor( aggregator.setShouldShareHistoryData.map { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } + // setShouldEncryptForInvitedMembers + aggregator.setShouldEncryptForInvitedMembersData.map { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value + } } cryptoStoreAggregator = null } From a386a4762c13ac13922382d7ee119de10b67dd67 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:18:32 +0100 Subject: [PATCH 10/92] Crypto store: Log realm transactions and the duration --- .../sdk/internal/crypto/store/db/Helper.kt | 12 ++- .../crypto/store/db/RealmCryptoStore.kt | 84 +++++++++---------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 2d66ce1488..6412df205f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -20,10 +20,12 @@ import android.util.Base64 import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject +import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream +import kotlin.system.measureTimeMillis /** * Get realm, invoke the action, close realm, and return the result of the action. @@ -55,10 +57,12 @@ internal fun doRealmQueryAndCopyList(realmConfiguration: Realm /** * Get realm instance, invoke the action in a transaction and close realm. */ -internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { action.invoke(it) } - } +internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { + measureTimeMillis { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { action.invoke(it) } + } + }.also { Timber.w("doRealmTransaction for $tag took $it millis") } } internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 3f9e22140e..c62dd7f5c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -147,7 +147,7 @@ internal class RealmCryptoStore @Inject constructor( init { // Ensure CryptoMetadataEntity is inserted in DB - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("init", realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() var deleteAll = false @@ -189,7 +189,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteStore() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("deleteStore", realmConfiguration) { it.deleteAll() } } @@ -218,7 +218,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeDeviceId(deviceId: String) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeDeviceId", realmConfiguration) { it.where().findFirst()?.deviceId = deviceId } } @@ -230,7 +230,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveOlmAccount() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveOlmAccount", realmConfiguration) { it.where().findFirst()?.putOlmAccount(olmAccount) } } @@ -248,7 +248,7 @@ internal class RealmCryptoStore @Inject constructor( @Synchronized override fun getOrCreateOlmAccount(): OlmAccount { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) { val metaData = it.where().findFirst() val existing = metaData!!.getOlmAccount() if (existing == null) { @@ -288,7 +288,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeUserDevices(userId: String, devices: Map?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> if (devices == null) { Timber.d("Remove user $userId") // Remove the user @@ -331,7 +331,7 @@ internal class RealmCryptoStore @Inject constructor( selfSigningKey: CryptoCrossSigningKey?, userSigningKey: CryptoCrossSigningKey? ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> UserEntity.getOrCreate(realm, userId) .let { userEntity -> if (masterKey == null || selfSigningKey == null) { @@ -480,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk xSignUserPrivateKey = usk @@ -490,7 +490,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKeyVersion = version @@ -516,7 +516,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeMSKPrivateKey(msk: String?) { Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk } @@ -525,7 +525,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeSSKPrivateKey(ssk: String?) { Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignSelfSignedPrivateKey = ssk } @@ -534,7 +534,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUSKPrivateKey(usk: String?) { Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } @@ -667,7 +667,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeRoomAlgorithm(roomId: String, algorithm: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeRoomAlgorithm", realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> entity.algorithm = algorithm // store anyway the new algorithm, but mark the room @@ -729,7 +729,7 @@ internal class RealmCryptoStore @Inject constructor( if (sessionIdentifier != null) { val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeSession", realmConfiguration) { val realmOlmSession = OlmSessionEntity().apply { primaryKey = key sessionId = sessionIdentifier @@ -786,7 +786,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm -> sessions.forEach { wrapper -> val sessionIdentifier = try { @@ -910,7 +910,7 @@ internal class RealmCryptoStore @Inject constructor( override fun removeInboundGroupSession(sessionId: String, senderKey: String) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("removeInboundGroupSession", realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findAll() @@ -929,7 +929,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeyBackupVersion(keyBackupVersion: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeyBackupVersion", realmConfiguration) { it.where().findFirst()?.backupVersion = keyBackupVersion } } @@ -941,7 +941,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeysBackupData", realmConfiguration) { if (keysBackupData == null) { // Clear the table it.where() @@ -955,7 +955,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun resetBackupMarkers() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("resetBackupMarkers", realmConfiguration) { it.where() .findAll() .map { inboundGroupSession -> @@ -969,7 +969,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { val sessionIdentifier = @@ -1028,13 +1028,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices = block } } override fun enableKeyGossiping(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableKeyGossiping", realmConfiguration) { it.where().findFirst()?.globalEnableKeyGossiping = enable } } @@ -1058,13 +1058,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun enableShareKeyOnInvite(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) { it.where().findFirst()?.enableKeyForwardingOnInvite = enable } } override fun setDeviceKeysUploaded(uploaded: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded } } @@ -1111,7 +1111,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId) ?.blacklistUnverifiedDevices = block } @@ -1131,7 +1131,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) { deviceTrackingStatuses .map { entry -> UserEntity.getOrCreate(it, entry.key) @@ -1264,7 +1264,7 @@ internal class RealmCryptoStore @Inject constructor( ): OutgoingKeyRequest { // Insert the request and return the one passed in parameter lateinit var request: OutgoingKeyRequest - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId) @@ -1302,7 +1302,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1316,7 +1316,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1333,7 +1333,7 @@ internal class RealmCryptoStore @Inject constructor( fromDevice: String?, event: Event ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId) @@ -1349,7 +1349,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequest(requestId: String) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.deleteOnCascade() @@ -1357,7 +1357,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name) .findAll() @@ -1493,7 +1493,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { userId -> addOrUpdateCrossSigningInfo(realm, userId, info) } @@ -1501,7 +1501,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm -> val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1521,7 +1521,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setDeviceTrust", realmConfiguration) { realm -> realm.where(DeviceInfoEntity::class.java) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst()?.let { deviceInfoEntity -> @@ -1541,7 +1541,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun clearOtherUserTrust() { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { info -> @@ -1556,7 +1556,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateUsersTrust(check: (String) -> Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateUsersTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { xInfoEntity -> @@ -1664,13 +1664,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm -> addOrUpdateCrossSigningInfo(realm, userId, info) } } override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { myUserId -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> val level = xInfoEntity.trustLevelEntity @@ -1709,7 +1709,7 @@ internal class RealmCryptoStore @Inject constructor( val roomId = withHeldContent.roomId ?: return val sessionId = withHeldContent.sessionId ?: return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm -> WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { it.code = withHeldContent.code it.senderKey = withHeldContent.senderKey @@ -1741,7 +1741,7 @@ internal class RealmCryptoStore @Inject constructor( deviceIdentityKey: String, chainIndex: Int ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, roomId = roomId, @@ -1790,7 +1790,7 @@ internal class RealmCryptoStore @Inject constructor( */ override fun tidyUpDataBase() { val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm -> // Clean the old ones? realm.where() From f26178fc211efb9f0fa7fe56267401fd2a2a494a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:21:03 +0100 Subject: [PATCH 11/92] Avoid useless transaction --- .../sdk/internal/crypto/store/db/CryptoStoreAggregator.kt | 7 ++++++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt index 165062f17c..687ec95ec3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -19,4 +19,9 @@ package org.matrix.android.sdk.internal.crypto.store.db data class CryptoStoreAggregator( val setShouldShareHistoryData: MutableMap = mutableMapOf(), val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), -) +) { + fun isEmpty(): Boolean { + return setShouldShareHistoryData.isEmpty() && + setShouldEncryptForInvitedMembersData.isEmpty() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index c62dd7f5c2..949043f2db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1824,7 +1824,11 @@ internal class RealmCryptoStore @Inject constructor( val aggregator = cryptoStoreAggregator ?: return Unit.also { Timber.e("cryptoStoreAggregator is null...") } + cryptoStoreAggregator = null + if (aggregator.isEmpty()) { + return + } doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> // setShouldShareHistory aggregator.setShouldShareHistoryData.map { @@ -1835,6 +1839,5 @@ internal class RealmCryptoStore @Inject constructor( CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } - cryptoStoreAggregator = null } } From 4c4ef0d73eca19e846c553456d9f9e500276328c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:57:39 +0100 Subject: [PATCH 12/92] Batch insertion of user data after downloading keys. --- .../sdk/internal/crypto/DeviceListManager.kt | 14 +- .../internal/crypto/store/IMXCryptoStore.kt | 2 + .../internal/crypto/store/UserDataToStore.kt | 25 ++ .../crypto/store/db/RealmCryptoStore.kt | 236 ++++++++++-------- 4 files changed, 165 insertions(+), 112 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 7e9e156003..f6e08ce9f7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore @@ -371,6 +372,8 @@ internal class DeviceListManager @Inject constructor( Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } + val userDataToStore = UserDataToStore() + for (userId in filteredUsers) { // al devices = val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } @@ -404,7 +407,7 @@ internal class DeviceListManager @Inject constructor( } // Update the store // Note that devices which aren't in the response will be removed from the stores - cryptoStore.storeUserDevices(userId, workingCopy) + userDataToStore.userDevices[userId] = workingCopy } val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { @@ -416,14 +419,11 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - cryptoStore.storeUserCrossSigningKeys( - userId, - masterKey, - selfSigningKey, - userSigningKey - ) + userDataToStore.userCrossSigningKeys[userId] = Triple(masterKey, selfSigningKey, userSigningKey) } + cryptoStore.storeUserDataToStore(userDataToStore) + // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a285dbec78..92a92ab36a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -583,4 +583,6 @@ internal interface IMXCryptoStore { fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + + fun storeUserDataToStore(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt new file mode 100644 index 0000000000..05afc75e5a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.crypto.store + +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo + +internal data class UserDataToStore( + val userDevices: MutableMap> = mutableMapOf(), + val userCrossSigningKeys: MutableMap> = mutableMapOf(), +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 949043f2db..83ff960d53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity @@ -289,37 +290,41 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserDevices(userId: String, devices: Map?) { doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> - if (devices == null) { - Timber.d("Remove user $userId") - // Remove the user - UserEntity.delete(realm, userId) - } else { - val userEntity = UserEntity.getOrCreate(realm, userId) - // First delete the removed devices - val deviceIds = devices.keys - userEntity.devices.toTypedArray().iterator().let { - while (it.hasNext()) { - val deviceInfoEntity = it.next() - if (deviceInfoEntity.deviceId !in deviceIds) { - Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") - deviceInfoEntity.deleteOnCascade() - } + storeUserDevices(realm, userId, devices) + } + } + + private fun storeUserDevices(realm: Realm, userId: String, devices: Map?) { + if (devices == null) { + Timber.d("Remove user $userId") + // Remove the user + UserEntity.delete(realm, userId) + } else { + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } } - // Then update existing devices or add new one - devices.values.forEach { cryptoDeviceInfo -> - val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } - if (existingDeviceInfoEntity == null) { - // Add the device - Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") - val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) - newEntity.firstTimeSeenLocalTs = clock.epochMillis() - userEntity.devices.add(newEntity) - } else { - // Update the device - Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") - CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) - } + } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = clock.epochMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) } } } @@ -332,85 +337,95 @@ internal class RealmCryptoStore @Inject constructor( userSigningKey: CryptoCrossSigningKey? ) { doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - UserEntity.getOrCreate(realm, userId) - .let { userEntity -> - if (masterKey == null || selfSigningKey == null) { - // The user has disabled cross signing? - userEntity.crossSigningInfoEntity?.deleteOnCascade() - userEntity.crossSigningInfoEntity = null - } else { - var shouldResetMyDevicesLocalTrust = false - CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> - // What should we do if we detect a change of the keys? - val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, masterKey) - } else { - Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(masterKey) - signingInfo.setMasterKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my msk has changed! clear my private key - // Could we have some race here? e.g I am the one that did change the keys - // could i get this update to early and clear the private keys? - // -> initializeCrossSigning is guarding for that by storing all at once - realm.where().findFirst()?.apply { - xSignMasterPrivateKey = null - } + storeUserCrossSigningKeys(realm, userId, masterKey, selfSigningKey, userSigningKey) + } + } + + private fun storeUserCrossSigningKeys( + realm: Realm, + userId: String, + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? + ) { + UserEntity.getOrCreate(realm, userId) + .let { userEntity -> + if (masterKey == null || selfSigningKey == null) { + // The user has disabled cross signing? + userEntity.crossSigningInfoEntity?.deleteOnCascade() + userEntity.crossSigningInfoEntity = null + } else { + var shouldResetMyDevicesLocalTrust = false + CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> + // What should we do if we detect a change of the keys? + val existingMaster = signingInfo.getMasterKey() + if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, masterKey) + } else { + Timber.d("## CrossSigning MSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(masterKey) + signingInfo.setMasterKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my msk has changed! clear my private key + // Could we have some race here? e.g I am the one that did change the keys + // could i get this update to early and clear the private keys? + // -> initializeCrossSigning is guarding for that by storing all at once + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = null } } - - val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) - } else { - Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(selfSigningKey) - signingInfo.setSelfSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my ssk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignSelfSignedPrivateKey = null - } - } - } - - // Only for me - if (userSigningKey != null) { - val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userSigningKey) - } else { - Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userSigningKey) - signingInfo.setUserSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my usk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignUserPrivateKey = null - } - } - } - } - - // When my cross signing keys are reset, we consider clearing all existing device trust - if (shouldResetMyDevicesLocalTrust) { - realm.where() - .equalTo(UserEntityFields.USER_ID, this.userId) - .findFirst() - ?.devices?.forEach { - it?.trustLevelEntity?.crossSignedVerified = false - it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId - } - } - userEntity.crossSigningInfoEntity = signingInfo } + + val existingSelfSigned = signingInfo.getSelfSignedKey() + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) + } else { + Timber.d("## CrossSigning SSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(selfSigningKey) + signingInfo.setSelfSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my ssk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = null + } + } + } + + // Only for me + if (userSigningKey != null) { + val existingUSK = signingInfo.getUserSigningKey() + if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userSigningKey) + } else { + Timber.d("## CrossSigning USK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userSigningKey) + signingInfo.setUserSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my usk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignUserPrivateKey = null + } + } + } + } + + // When my cross signing keys are reset, we consider clearing all existing device trust + if (shouldResetMyDevicesLocalTrust) { + realm.where() + .equalTo(UserEntityFields.USER_ID, this.userId) + .findFirst() + ?.devices?.forEach { + it?.trustLevelEntity?.crossSignedVerified = false + it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId + } + } + userEntity.crossSigningInfoEntity = signingInfo } } - } + } } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { @@ -1831,13 +1846,24 @@ internal class RealmCryptoStore @Inject constructor( } doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> // setShouldShareHistory - aggregator.setShouldShareHistoryData.map { + aggregator.setShouldShareHistoryData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } // setShouldEncryptForInvitedMembers - aggregator.setShouldEncryptForInvitedMembersData.map { + aggregator.setShouldEncryptForInvitedMembersData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } } + + override fun storeUserDataToStore(userDataToStore: UserDataToStore) { + doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> + userDataToStore.userDevices.forEach { + storeUserDevices(realm, it.key, it.value) + } + userDataToStore.userCrossSigningKeys.forEach { + storeUserCrossSigningKeys(realm, it.key, it.value.first, it.value.second, it.value.third) + } + } + } } From 02e7157206404ea07c2544ac6e590c30ae201f98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 16:16:17 +0100 Subject: [PATCH 13/92] Introduce CryptoCrossSigningKeys container --- .../crosssigning/CryptoCrossSigningKeys.kt | 26 ++++++++++++++ .../sdk/internal/crypto/DeviceListManager.kt | 7 +++- .../internal/crypto/store/IMXCryptoStore.kt | 5 ++- .../internal/crypto/store/UserDataToStore.kt | 4 +-- .../crypto/store/db/RealmCryptoStore.kt | 36 +++++++++---------- 5 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt new file mode 100644 index 0000000000..e0a422b54b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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.crypto.crosssigning + +/** + * Container for the three cross signing keys: master, self signing and user signing. + */ +data class CryptoCrossSigningKeys( + val masterKey: CryptoCrossSigningKey?, + val selfSigningKey: CryptoCrossSigningKey?, + val userSigningKey: CryptoCrossSigningKey?, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index f6e08ce9f7..8ced0864d0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap @@ -419,7 +420,11 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - userDataToStore.userCrossSigningKeys[userId] = Triple(masterKey, selfSigningKey, userSigningKey) + userDataToStore.userCrossSigningKeys[userId] = CryptoCrossSigningKeys( + masterKey = masterKey, + selfSigningKey = selfSigningKey, + userSigningKey = userSigningKey + ) } cryptoStore.storeUserDataToStore(userDataToStore) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 92a92ab36a..1a7f4a5320 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo @@ -235,9 +236,7 @@ internal interface IMXCryptoStore { fun storeUserCrossSigningKeys( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + cryptoCrossSigningKeys: CryptoCrossSigningKeys ) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index 05afc75e5a..d4b8308650 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.crypto.store -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( val userDevices: MutableMap> = mutableMapOf(), - val userCrossSigningKeys: MutableMap> = mutableMapOf(), + val userCrossSigningKeys: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 83ff960d53..06ae2c00b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo @@ -332,25 +332,21 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserCrossSigningKeys( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + cryptoCrossSigningKeys: CryptoCrossSigningKeys, ) { doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - storeUserCrossSigningKeys(realm, userId, masterKey, selfSigningKey, userSigningKey) + storeUserCrossSigningKeys(realm, userId, cryptoCrossSigningKeys) } } private fun storeUserCrossSigningKeys( realm: Realm, userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + keys: CryptoCrossSigningKeys, ) { UserEntity.getOrCreate(realm, userId) .let { userEntity -> - if (masterKey == null || selfSigningKey == null) { + if (keys.masterKey == null || keys.selfSigningKey == null) { // The user has disabled cross signing? userEntity.crossSigningInfoEntity?.deleteOnCascade() userEntity.crossSigningInfoEntity = null @@ -359,11 +355,11 @@ internal class RealmCryptoStore @Inject constructor( CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, masterKey) + if (existingMaster != null && existingMaster.publicKeyBase64 == keys.masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, keys.masterKey) } else { Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(masterKey) + val keyEntity = crossSigningKeysMapper.map(keys.masterKey) signingInfo.setMasterKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -378,11 +374,11 @@ internal class RealmCryptoStore @Inject constructor( } val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == keys.selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, keys.selfSigningKey) } else { Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(selfSigningKey) + val keyEntity = crossSigningKeysMapper.map(keys.selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -394,13 +390,13 @@ internal class RealmCryptoStore @Inject constructor( } // Only for me - if (userSigningKey != null) { + if (keys.userSigningKey != null) { val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userSigningKey) + if (existingUSK != null && existingUSK.publicKeyBase64 == keys.userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, keys.userSigningKey) } else { Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userSigningKey) + val keyEntity = crossSigningKeysMapper.map(keys.userSigningKey) signingInfo.setUserSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -1862,7 +1858,7 @@ internal class RealmCryptoStore @Inject constructor( storeUserDevices(realm, it.key, it.value) } userDataToStore.userCrossSigningKeys.forEach { - storeUserCrossSigningKeys(realm, it.key, it.value.first, it.value.second, it.value.third) + storeUserCrossSigningKeys(realm, it.key, it.value) } } } From 06f3c1101042500973c1999f8b9ae0f97a42332e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 16:43:09 +0100 Subject: [PATCH 14/92] Changelog --- changelog.d/7879.bugfix | 1 + .../matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/7879.bugfix diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix new file mode 100644 index 0000000000..be828ec2cc --- /dev/null +++ b/changelog.d/7879.bugfix @@ -0,0 +1 @@ +Reduce number of crypto database transactions when handling the sync response diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 1a7f4a5320..4ffd93875b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo From 437b93cc18751777e92f7a249dd67c93460382e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Jan 2023 11:35:04 +0100 Subject: [PATCH 15/92] Add some doc --- .../internal/crypto/store/IMXCryptoStore.kt | 19 +++++++++++++++++++ .../internal/crypto/store/UserDataToStore.kt | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 4ffd93875b..a74a7f2906 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -48,7 +48,19 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { + /** + * Notify the store that a sync response treatment is starting. + * Impacted methods: + * - [setShouldShareHistory] + * - [setShouldEncryptForInvitedMembers] + * @See [onSyncCompleted] to notify that the treatment is over. + */ fun onSyncWillProcess() + + /** + * Notify the store that the sync treatment response is finished. + * The store will save all aggregated data. + */ fun onSyncCompleted() /** @@ -291,6 +303,9 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean + /** + * The data is not stored immediately, this MUST be call during a sync response treatment. + */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) fun shouldShareHistory(roomId: String): Boolean @@ -298,6 +313,7 @@ internal interface IMXCryptoStore { /** * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) * will be shared to new user invites. + * The data is not stored immediately, this MUST be call during a sync response treatment. * * @param roomId the room id * @param shouldShareHistory The boolean flag @@ -582,5 +598,8 @@ internal interface IMXCryptoStore { fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + /** + * Store a bunch of data related to the users. @See [UserDataToStore]. + */ fun storeUserDataToStore(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index d4b8308650..89cbe4e826 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -20,6 +20,12 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigning import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( + /** + * Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]). + */ val userDevices: MutableMap> = mutableMapOf(), + /** + * Map of userId -> [CryptoCrossSigningKeys]. + */ val userCrossSigningKeys: MutableMap = mutableMapOf(), ) From e903dac22480525c97ba7ccc90cde41eeb811491 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 15:40:59 +0100 Subject: [PATCH 16/92] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.wip diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..4dc55708be --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1 @@ +[Poll] Render active polls list of a room From cba960fbd782c91ab7de7e84c3635d3374ce3de2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:05:52 +0100 Subject: [PATCH 17/92] Adding new entry "Poll history" into room profile screen --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../app/features/roomprofile/RoomProfileController.kt | 7 +++++++ .../vector/app/features/roomprofile/RoomProfileFragment.kt | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..d7e56fcbb0 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2335,6 +2335,7 @@ "One person" "%1$d people" + Poll history Uploads Leave Room Leave diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index eb43a345f2..87f5657fc8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -56,6 +56,7 @@ class RoomProfileController @Inject constructor( fun onMemberListClicked() fun onBannedMemberListClicked() fun onNotificationsClicked() + fun onPollHistoryClicked() fun onUploadsClicked() fun createShortcut() fun onSettingsClicked() @@ -263,6 +264,12 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } + buildProfileAction( + id = "poll_history", + title = stringProvider.getString(R.string.room_profile_section_more_polls), + icon = R.drawable.ic_attachment_poll, + action = { callback?.onPollHistoryClicked() } + ) buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f4394111ab..a06fdf9152 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -269,6 +269,10 @@ class RoomProfileFragment : roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificationSettings) } + override fun onPollHistoryClicked() { + // TODO navigate to new screen + } + override fun onUploadsClicked() { roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads) } From 7436c2e1f5a60ed8507c9df53b49bec014182e07 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:41:42 +0100 Subject: [PATCH 18/92] Navigate to new empty screen --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../roomprofile/RoomProfileActivity.kt | 6 ++ .../roomprofile/RoomProfileFragment.kt | 2 +- .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/polls/RoomPollsAction.kt | 21 +++++++ .../roomprofile/polls/RoomPollsFragment.kt | 61 +++++++++++++++++++ .../roomprofile/polls/RoomPollsViewEvent.kt | 21 +++++++ .../roomprofile/polls/RoomPollsViewModel.kt | 41 +++++++++++++ .../roomprofile/polls/RoomPollsViewState.kt | 27 ++++++++ .../main/res/layout/fragment_room_polls.xml | 30 +++++++++ 10 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt create mode 100644 vector/src/main/res/layout/fragment_room_polls.xml 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 d22ab51e7a..911bbfa4a3 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 @@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -697,4 +698,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(SetLinkViewModel::class) fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(RoomPollsViewModel::class) + fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } 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 526d676dee..3c37c92650 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 @@ -36,6 +36,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment +import im.vector.app.features.roomprofile.polls.RoomPollsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -98,6 +99,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomPolls -> openRoomPolls() RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() @@ -126,6 +128,10 @@ class RoomProfileActivity : finish() } + private fun openRoomPolls() { + addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) + } + private fun openRoomUploads() { addFragmentToBackstack(views.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index a06fdf9152..51885dbf39 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -270,7 +270,7 @@ class RoomProfileFragment : } override fun onPollHistoryClicked() { - // TODO navigate to new screen + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPolls) } override fun onUploadsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 7d62bb86a1..b243ceb206 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -25,6 +25,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() + object OpenRoomPolls : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt new file mode 100644 index 0000000000..895079ec90 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class RoomPollsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt new file mode 100644 index 0000000000..a33dfdf93d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsBinding +import im.vector.app.features.roomprofile.RoomProfileArgs + +@AndroidEntryPoint +class RoomPollsFragment : VectorBaseFragment() { + + private val roomProfileArgs: RoomProfileArgs by args() + + private val viewModel: RoomPollsViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { + return FragmentRoomPollsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar() + setupTabs() + } + + private fun setupToolbar() { + setupToolbar(views.roomPollsToolbar) + .allowBack() + } + + private fun setupTabs() { + // TODO + } + + override fun invalidate() = withState(viewModel) { + // TODO + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt new file mode 100644 index 0000000000..3896abf84c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls + +import im.vector.app.core.platform.VectorViewEvents + +sealed class RoomPollsViewEvent : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt new file mode 100644 index 0000000000..cfda5af0cf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls + +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.VectorViewModel + +class RoomPollsViewModel @AssistedInject constructor( + @Assisted initialState: RoomPollsViewState, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomPollsViewState): RoomPollsViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: RoomPollsAction) { + // do nothing for now + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt new file mode 100644 index 0000000000..0233ddb693 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -0,0 +1,27 @@ +/* + * 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.roomprofile.polls + +import com.airbnb.mvrx.MavericksState +import im.vector.app.features.roomprofile.RoomProfileArgs + +data class RoomPollsViewState( + val roomId: String, +) : MavericksState { + + constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) +} diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml new file mode 100644 index 0000000000..b1b91e9a18 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + From 10133bd20ffe12d059049d906079f3e74e57990f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 17:46:07 +0100 Subject: [PATCH 19/92] Setup tab layout when landing on the room polls screen --- .../src/main/res/values/strings.xml | 1 + .../roomprofile/polls/RoomPollsAction.kt | 2 +- .../roomprofile/polls/RoomPollsFragment.kt | 24 +++++++++---- .../polls/RoomPollsPagerAdapter.kt | 36 +++++++++++++++++++ .../roomprofile/polls/RoomPollsViewEvent.kt | 2 +- .../roomprofile/polls/RoomPollsViewModel.kt | 2 +- .../polls/active/RoomActivePollsFragment.kt | 35 ++++++++++++++++++ .../main/res/layout/fragment_room_polls.xml | 32 ++++++++++++++--- .../res/layout/fragment_room_polls_list.xml | 7 ++++ 9 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt create mode 100644 vector/src/main/res/layout/fragment_room_polls_list.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d7e56fcbb0..e0f2f7b288 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3191,6 +3191,7 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll + Active polls Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 895079ec90..9d87f13f14 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index a33dfdf93d..7617da71df 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. @@ -22,8 +22,9 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState +import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsBinding import im.vector.app.features.roomprofile.RoomProfileArgs @@ -35,6 +36,8 @@ class RoomPollsFragment : VectorBaseFragment() { private val viewModel: RoomPollsViewModel by fragmentViewModel() + private var tabLayoutMediator: TabLayoutMediator? = null + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { return FragmentRoomPollsBinding.inflate(inflater, container, false) } @@ -46,16 +49,25 @@ class RoomPollsFragment : VectorBaseFragment() { setupTabs() } + override fun onDestroyView() { + views.roomPollsViewPager.adapter = null + tabLayoutMediator?.detach() + tabLayoutMediator = null + super.onDestroyView() + } + private fun setupToolbar() { setupToolbar(views.roomPollsToolbar) .allowBack() } private fun setupTabs() { - // TODO - } + views.roomPollsViewPager.adapter = RoomPollsPagerAdapter(this) - override fun invalidate() = withState(viewModel) { - // TODO + tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> + when (position) { + 0 -> tab.text = getString(R.string.active_polls) + } + }.also { it.attach() } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt new file mode 100644 index 0000000000..5472782079 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt @@ -0,0 +1,36 @@ +/* + * 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.roomprofile.polls + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment + +class RoomPollsPagerAdapter( + private val fragment: Fragment +) : FragmentStateAdapter(fragment) { + + override fun getItemCount() = 1 + + override fun createFragment(position: Int): Fragment { + return instantiateFragment(RoomActivePollsFragment::class.java.name) + } + + private fun instantiateFragment(fragmentName: String): Fragment { + return fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, fragmentName) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index 3896abf84c..231123563a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index cfda5af0cf..42278ff976 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt new file mode 100644 index 0000000000..230da49b22 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -0,0 +1,35 @@ +/* + * 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.roomprofile.polls.active + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.airbnb.mvrx.parentFragmentViewModel +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel + +@AndroidEntryPoint +class RoomActivePollsFragment : VectorBaseFragment() { + + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { + return FragmentRoomPollsListBinding.inflate(inflater, container, false) + } +} diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index b1b91e9a18..96a94cd9c5 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -1,7 +1,6 @@ @@ -21,10 +20,35 @@ + + + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/roomPollsTabs" /> diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml new file mode 100644 index 0000000000..1d672087a9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -0,0 +1,7 @@ + + + + + From 9f97579f9dc4c928fd8f95449fde3a10b8f77040 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:07:50 +0100 Subject: [PATCH 20/92] Epoxy model for active poll --- .../polls/active/ActivePollItem.kt | 51 +++++++++++++++++++ .../src/main/res/layout/item_poll_active.xml | 42 +++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt create mode 100644 vector/src/main/res/layout/item_poll_active.xml diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt new file mode 100644 index 0000000000..2a927653f1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt @@ -0,0 +1,51 @@ +/* + * 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.roomprofile.polls.active + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +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 + +@EpoxyModelClass +abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll_active) { + + @EpoxyAttribute + lateinit var formattedDate: String + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(clickListener) + holder.date.text = formattedDate + holder.title.text = title + } + + class Holder : VectorEpoxyHolder() { + val date by bind(R.id.pollActiveDate) + val title by bind(R.id.pollActiveTitle) + } +} diff --git a/vector/src/main/res/layout/item_poll_active.xml b/vector/src/main/res/layout/item_poll_active.xml new file mode 100644 index 0000000000..8cf6c9e576 --- /dev/null +++ b/vector/src/main/res/layout/item_poll_active.xml @@ -0,0 +1,42 @@ + + + + + + + + + + From 7b63f891c33ffbb28d42491ac4ac48b89aef0a4e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:42:42 +0100 Subject: [PATCH 21/92] Epoxy controller to render active poll list --- .../features/roomprofile/polls/PollSummary.kt | 25 ++++++++++ .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/active/RoomActivePollsController.kt | 49 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt new file mode 100644 index 0000000000..3eb45c6144 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.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.roomprofile.polls + +sealed interface PollSummary { + data class ActivePoll( + val id: String, + val creationTimestamp: Long, + val title: String, + ) : PollSummary +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 0233ddb693..74794c99b1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -21,6 +21,7 @@ import im.vector.app.features.roomprofile.RoomProfileArgs data class RoomPollsViewState( val roomId: String, + val polls: List = emptyList(), ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt new file mode 100644 index 0000000000..9f26569e58 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -0,0 +1,49 @@ +/* + * 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.roomprofile.polls.active + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.features.roomprofile.polls.PollSummary +import javax.inject.Inject + +class RoomActivePollsController @Inject constructor( + val dateFormatter: VectorDateFormatter, +) : TypedEpoxyController>() { + + interface Listener { + fun onPollClicked(pollId: String) + } + + var listener: Listener? = null + + override fun buildModels(data: List?) { + if (data == null) return + + val host = this + data.forEach { poll -> + activePollItem { + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } + } +} From f20513eb16602a7b05d02231ca784215f0a28551 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:56:44 +0100 Subject: [PATCH 22/92] Render the active polls list on fragment --- .../polls/active/RoomActivePollsFragment.kt | 45 ++++++++++++++++++- .../res/layout/fragment_room_polls_list.xml | 13 ++++++ .../src/main/res/layout/item_poll_active.xml | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 230da49b22..10518d4b18 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -16,20 +16,63 @@ package im.vector.app.features.roomprofile.polls.active +import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import javax.inject.Inject @AndroidEntryPoint -class RoomActivePollsFragment : VectorBaseFragment() { +class RoomActivePollsFragment : + VectorBaseFragment(), + RoomActivePollsController.Listener { + + @Inject + lateinit var roomActivePollsController: RoomActivePollsController private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupList() + } + + private fun setupList() { + roomActivePollsController.listener = this + views.activePollsList.adapter = roomActivePollsController.adapter + } + + override fun onDestroyView() { + cleanUpList() + super.onDestroyView() + } + + private fun cleanUpList() { + views.activePollsList.cleanup() + roomActivePollsController.listener = null + } + + override fun invalidate() = withState(viewModel) { viewState -> + renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + } + + private fun renderList(polls: List) { + roomActivePollsController.setData(polls) + } + + override fun onPollClicked(pollId: String) { + // TODO navigate to details + } } diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 1d672087a9..39add6a298 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -1,7 +1,20 @@ + + diff --git a/vector/src/main/res/layout/item_poll_active.xml b/vector/src/main/res/layout/item_poll_active.xml index 8cf6c9e576..2db6450f94 100644 --- a/vector/src/main/res/layout/item_poll_active.xml +++ b/vector/src/main/res/layout/item_poll_active.xml @@ -10,6 +10,7 @@ android:id="@+id/pollActiveDate" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="32dp" android:textAppearance="@style/TextAppearance.Vector.Caption" android:textColor="?vctr_content_tertiary" app:layout_constraintStart_toStartOf="parent" From 77d3b7da04b0e8e2cd9ff6ab44392565675eef11 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:40:41 +0100 Subject: [PATCH 23/92] Fix missing id in Epoxy model --- .../roomprofile/polls/active/RoomActivePollsController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index 9f26569e58..fd32ae51d0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -38,6 +38,7 @@ class RoomActivePollsController @Inject constructor( val host = this data.forEach { poll -> activePollItem { + id(poll.id) formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) title(poll.title) clickListener { From 8de86e74807339f5a02abcea6fd5c1452ffc68c9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:59:48 +0100 Subject: [PATCH 24/92] Render mocked data get from use case --- .../roomprofile/polls/GetPollsUseCase.kt | 65 +++++++++++++++++++ .../roomprofile/polls/RoomPollsAction.kt | 4 +- .../roomprofile/polls/RoomPollsFilter.kt | 22 +++++++ .../roomprofile/polls/RoomPollsViewModel.kt | 23 ++++++- .../polls/active/RoomActivePollsFragment.kt | 10 ++- .../res/layout/fragment_room_polls_list.xml | 3 +- 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt new file mode 100644 index 0000000000..fa8c6d0aa6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -0,0 +1,65 @@ +/* + * 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.roomprofile.polls + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class GetPollsUseCase @Inject constructor() { + + fun execute(filter: RoomPollsFilter): Flow> { + // TODO unmock and add unit tests + return when (filter) { + RoomPollsFilter.ACTIVE -> getActivePolls() + RoomPollsFilter.ENDED -> emptyFlow() + }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } + } + + private fun getActivePolls(): Flow> { + return flowOf( + listOf( + PollSummary.ActivePoll( + id = "id1", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.ActivePoll( + id = "id2", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Which sport should the pupils do this year?" + ), + PollSummary.ActivePoll( + id = "id3", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), + PollSummary.ActivePoll( + id = "id4", + // 2022/06/22 UTC+1 + creationTimestamp = 1655848800000, + title = "What film should we show at the end of the year party?" + ), + ) + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 9d87f13f14..27753b6d16 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -18,4 +18,6 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction -sealed class RoomPollsAction : VectorViewModelAction +sealed interface RoomPollsAction : VectorViewModelAction { + data class SetFilter(val filter: RoomPollsFilter) : RoomPollsAction +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt new file mode 100644 index 0000000000..68ebb13f7d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.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 im.vector.app.features.roomprofile.polls + +enum class RoomPollsFilter { + ACTIVE, + ENDED, +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 42278ff976..27ba6679d8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -23,9 +23,13 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class RoomPollsViewModel @AssistedInject constructor( @Assisted initialState: RoomPollsViewState, + private val getPollsUseCase: GetPollsUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -35,7 +39,24 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private var pollsCollectionJob: Job? = null + + // TODO add unit tests override fun handle(action: RoomPollsAction) { - // do nothing for now + when (action) { + is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) + } + } + + override fun onCleared() { + pollsCollectionJob = null + super.onCleared() + } + + private fun handleSetFilter(filter: RoomPollsFilter) { + pollsCollectionJob?.cancel() + pollsCollectionJob = getPollsUseCase.execute(filter) + .onEach { setState { copy(polls = it) } } + .launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 10518d4b18..ed851a045d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -24,9 +24,12 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary +import im.vector.app.features.roomprofile.polls.RoomPollsAction +import im.vector.app.features.roomprofile.polls.RoomPollsFilter import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import javax.inject.Inject @@ -51,7 +54,7 @@ class RoomActivePollsFragment : private fun setupList() { roomActivePollsController.listener = this - views.activePollsList.adapter = roomActivePollsController.adapter + views.activePollsList.configureWith(roomActivePollsController) } override fun onDestroyView() { @@ -64,6 +67,11 @@ class RoomActivePollsFragment : roomActivePollsController.listener = null } + override fun onResume() { + super.onResume() + viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilter.ACTIVE)) + } + override fun invalidate() = withState(viewModel) { viewState -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) } diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 39add6a298..1aa6625ae5 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -8,8 +8,9 @@ Date: Fri, 30 Dec 2022 12:08:55 +0100 Subject: [PATCH 25/92] Allow access of poll history only in debug variant --- .../roomprofile/RoomProfileController.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 87f5657fc8..30bd6c7ed3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.epoxy.expandableTextItem import im.vector.app.core.epoxy.profiles.buildProfileAction @@ -264,12 +265,15 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } - buildProfileAction( - id = "poll_history", - title = stringProvider.getString(R.string.room_profile_section_more_polls), - icon = R.drawable.ic_attachment_poll, - action = { callback?.onPollHistoryClicked() } - ) + if (BuildConfig.DEBUG) { + // WIP, will be in release when related screens will be finished + buildProfileAction( + id = "poll_history", + title = stringProvider.getString(R.string.room_profile_section_more_polls), + icon = R.drawable.ic_attachment_poll, + action = { callback?.onPollHistoryClicked() } + ) + } buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), From 71b7edc6f2c095f476fda596cdc6cbaf2b3ca159 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 12:12:57 +0100 Subject: [PATCH 26/92] Adding debug log --- .../roomprofile/polls/active/RoomActivePollsFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index ed851a045d..b4e812a49e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsFilter import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -82,5 +83,6 @@ class RoomActivePollsFragment : override fun onPollClicked(pollId: String) { // TODO navigate to details + Timber.d("poll with id $pollId clicked") } } From bc985aa1ef310e5fe45cbaefc4b4cab71fa71dc8 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:19:50 +0100 Subject: [PATCH 27/92] Adding unit tests for ViewModel --- .../roomprofile/polls/RoomPollsViewModel.kt | 5 +- .../polls/RoomPollsViewModelTest.kt | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 27ba6679d8..7def7a508d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.polls +import androidx.annotation.VisibleForTesting import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -39,9 +40,9 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - private var pollsCollectionJob: Job? = null + @VisibleForTesting + var pollsCollectionJob: Job? = null - // TODO add unit tests override fun handle(action: RoomPollsAction) { when (action) { is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt new file mode 100644 index 0000000000..54b2a60d55 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.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.roomprofile.polls + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.flowOf +import org.amshove.kluent.shouldNotBeNull +import org.junit.Rule +import org.junit.Test + +private const val ROOM_ID = "room-id" + +class RoomPollsViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val fakeGetPollsUseCase = mockk() + private val initialState = RoomPollsViewState(ROOM_ID) + + private fun createViewModel(): RoomPollsViewModel { + return RoomPollsViewModel( + initialState = initialState, + getPollsUseCase = fakeGetPollsUseCase, + ) + } + + @Test + fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { + // Given + val filter = RoomPollsFilter.ACTIVE + val action = RoomPollsAction.SetFilter(filter = filter) + val polls = listOf(givenAPollSummary()) + every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) + val viewModel = createViewModel() + val expectedViewState = initialState.copy(polls = polls) + + // When + val viewModelTest = viewModel.test() + viewModel.pollsCollectionJob = null + viewModel.handle(action) + + // Then + viewModelTest + .assertLatestState(expectedViewState) + .finish() + viewModel.pollsCollectionJob.shouldNotBeNull() + verify { + viewModel.pollsCollectionJob?.cancel() + fakeGetPollsUseCase.execute(filter) + } + } + + private fun givenAPollSummary(): PollSummary { + return mockk() + } +} From e0b77936c1e4325387a1e06e4d4fef34bd7acfdd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:27:11 +0100 Subject: [PATCH 28/92] Changing the date format --- .../roomprofile/polls/active/RoomActivePollsController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index fd32ae51d0..2fab886282 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -39,7 +39,7 @@ class RoomActivePollsController @Inject constructor( data.forEach { poll -> activePollItem { id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) title(poll.title) clickListener { host.listener?.onPollClicked(poll.id) From bd9c53a96c96aa870d3edcb8db44a52f6afb2d42 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:57:37 +0100 Subject: [PATCH 29/92] Show message when list is empty --- .../src/main/res/values/strings.xml | 3 ++- .../roomprofile/polls/RoomPollsFragment.kt | 2 +- .../polls/active/RoomActivePollsFragment.kt | 8 +++++-- .../res/layout/fragment_room_polls_list.xml | 23 ++++++++++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e0f2f7b288..43507e60ce 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3191,7 +3191,8 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll - Active polls + Active polls + There are no active polls in this room Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 7617da71df..5c150f4391 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -66,7 +66,7 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - 0 -> tab.text = getString(R.string.active_polls) + 0 -> tab.text = getString(R.string.room_polls_active) } }.also { it.attach() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index b4e812a49e..4cc318edf9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -20,9 +20,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible 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.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -55,7 +57,8 @@ class RoomActivePollsFragment : private fun setupList() { roomActivePollsController.listener = this - views.activePollsList.configureWith(roomActivePollsController) + views.roomPollsList.configureWith(roomActivePollsController) + views.roomPollsEmptyTitle.text = getString(R.string.room_polls_active_no_item) } override fun onDestroyView() { @@ -64,7 +67,7 @@ class RoomActivePollsFragment : } private fun cleanUpList() { - views.activePollsList.cleanup() + views.roomPollsList.cleanup() roomActivePollsController.listener = null } @@ -79,6 +82,7 @@ class RoomActivePollsFragment : private fun renderList(polls: List) { roomActivePollsController.setData(polls) + views.roomPollsEmptyTitle.isVisible = polls.isEmpty() } override fun onPollClicked(pollId: String) { diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 1aa6625ae5..6949bb0c67 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent"> + + + From 6c0c5e506408d242ce1792ddd833c41836e573c3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:12:12 +0100 Subject: [PATCH 30/92] Rename poll item layout to be more generic --- .../app/features/roomprofile/polls/active/ActivePollItem.kt | 2 +- vector/src/main/res/layout/fragment_room_polls_list.xml | 2 +- .../src/main/res/layout/{item_poll_active.xml => item_poll.xml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename vector/src/main/res/layout/{item_poll_active.xml => item_poll.xml} (100%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt index 2a927653f1..35b1ecd6e1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt @@ -26,7 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick @EpoxyModelClass -abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll_active) { +abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll) { @EpoxyAttribute lateinit var formattedDate: String diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 6949bb0c67..8eb27e5e00 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -15,7 +15,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:itemCount="5" - tools:listitem="@layout/item_poll_active" /> + tools:listitem="@layout/item_poll" /> Date: Fri, 30 Dec 2022 15:48:14 +0100 Subject: [PATCH 31/92] Replace usage of colorAccent --- vector/src/main/res/layout/fragment_room_polls.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index 96a94cd9c5..dcaf483251 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -24,22 +24,22 @@ android:id="@+id/roomPollsTabs" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="20dp" android:layout_marginHorizontal="10dp" + android:layout_marginTop="20dp" android:background="?android:colorBackground" app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appBarLayout" + app:tabGravity="start" app:tabIndicatorFullWidth="false" app:tabIndicatorHeight="1dp" - app:tabPaddingBottom="-15dp" - app:tabTextColor="?vctr_content_primary" - app:tabSelectedTextColor="?colorAccent" - app:tabTextAppearance="@style/TextAppearance.Vector.Body" - app:tabGravity="start" app:tabMaxWidth="0dp" - app:tabMode="scrollable" /> + app:tabMode="scrollable" + app:tabPaddingBottom="-15dp" + app:tabSelectedTextColor="?colorSecondary" + app:tabTextAppearance="@style/TextAppearance.Vector.Body" + app:tabTextColor="?vctr_content_primary" /> Date: Fri, 30 Dec 2022 16:45:28 +0100 Subject: [PATCH 32/92] Ignore missing ContentDescription --- .../roomprofile/polls/active/RoomActivePollsController.kt | 4 +++- vector/src/main/res/layout/item_poll.xml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index 2fab886282..dc14ec366d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -33,7 +33,9 @@ class RoomActivePollsController @Inject constructor( var listener: Listener? = null override fun buildModels(data: List?) { - if (data == null) return + if (data.isNullOrEmpty()) { + return + } val host = this data.forEach { poll -> diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 2db6450f94..05e9b3a62a 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -25,7 +25,8 @@ android:src="@drawable/ic_attachment_poll" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/pollActiveDate" - app:tint="?vctr_content_secondary" /> + app:tint="?vctr_content_secondary" + tools:ignore="ContentDescription" /> Date: Thu, 5 Jan 2023 09:48:25 +0100 Subject: [PATCH 33/92] Rename fun --- .../org/matrix/android/sdk/internal/crypto/DeviceListManager.kt | 2 +- .../matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt | 2 +- .../android/sdk/internal/crypto/store/db/RealmCryptoStore.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 8ced0864d0..bb8acbf4b0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -427,7 +427,7 @@ internal class DeviceListManager @Inject constructor( ) } - cryptoStore.storeUserDataToStore(userDataToStore) + cryptoStore.storeData(userDataToStore) // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a74a7f2906..0c20ae79aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -601,5 +601,5 @@ internal interface IMXCryptoStore { /** * Store a bunch of data related to the users. @See [UserDataToStore]. */ - fun storeUserDataToStore(userDataToStore: UserDataToStore) + fun storeData(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 06ae2c00b1..615df6a5b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1852,7 +1852,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeUserDataToStore(userDataToStore: UserDataToStore) { + override fun storeData(userDataToStore: UserDataToStore) { doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) From 30940cb9370587b50fe2b5f05652032eaf0c1062 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 09:53:12 +0100 Subject: [PATCH 34/92] Rename `UserCrossSigningKeys` to `UserIdentity` --- ...ptoCrossSigningKeys.kt => UserIdentity.kt} | 2 +- .../sdk/internal/crypto/DeviceListManager.kt | 4 +- .../internal/crypto/store/IMXCryptoStore.kt | 9 +++-- .../internal/crypto/store/UserDataToStore.kt | 6 +-- .../crypto/store/db/RealmCryptoStore.kt | 40 +++++++++---------- 5 files changed, 32 insertions(+), 29 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/{CryptoCrossSigningKeys.kt => UserIdentity.kt} (96%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt index e0a422b54b..071db7f902 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning /** * Container for the three cross signing keys: master, self signing and user signing. */ -data class CryptoCrossSigningKeys( +data class UserIdentity( val masterKey: CryptoCrossSigningKey?, val selfSigningKey: CryptoCrossSigningKey?, val userSigningKey: CryptoCrossSigningKey?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index bb8acbf4b0..364d77f7ac 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -24,8 +24,8 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity 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.internal.crypto.model.CryptoInfoMapper @@ -420,7 +420,7 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - userDataToStore.userCrossSigningKeys[userId] = CryptoCrossSigningKeys( + userDataToStore.userIdentities[userId] = UserIdentity( masterKey = masterKey, selfSigningKey = selfSigningKey, userSigningKey = userSigningKey diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0c20ae79aa..10158c7a4d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -245,9 +245,12 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) - fun storeUserCrossSigningKeys( + /** + * Store the cross signing keys for the user userId. + */ + fun storeUserIdentity( userId: String, - cryptoCrossSigningKeys: CryptoCrossSigningKeys + userIdentity: UserIdentity ) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index 89cbe4e826..914ce4704e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.store -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( @@ -25,7 +25,7 @@ internal data class UserDataToStore( */ val userDevices: MutableMap> = mutableMapOf(), /** - * Map of userId -> [CryptoCrossSigningKeys]. + * Map of userId -> [UserIdentity]. */ - val userCrossSigningKeys: MutableMap = mutableMapOf(), + val userIdentities: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 615df6a5b9..1be88249eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -330,23 +330,23 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeUserCrossSigningKeys( + override fun storeUserIdentity( userId: String, - cryptoCrossSigningKeys: CryptoCrossSigningKeys, + userIdentity: UserIdentity, ) { - doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - storeUserCrossSigningKeys(realm, userId, cryptoCrossSigningKeys) + doRealmTransaction("storeUserIdentity", realmConfiguration) { realm -> + storeUserIdentity(realm, userId, userIdentity) } } - private fun storeUserCrossSigningKeys( + private fun storeUserIdentity( realm: Realm, userId: String, - keys: CryptoCrossSigningKeys, + userIdentity: UserIdentity, ) { UserEntity.getOrCreate(realm, userId) .let { userEntity -> - if (keys.masterKey == null || keys.selfSigningKey == null) { + if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) { // The user has disabled cross signing? userEntity.crossSigningInfoEntity?.deleteOnCascade() userEntity.crossSigningInfoEntity = null @@ -355,11 +355,11 @@ internal class RealmCryptoStore @Inject constructor( CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == keys.masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, keys.masterKey) + if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey) } else { Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.masterKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey) signingInfo.setMasterKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -374,11 +374,11 @@ internal class RealmCryptoStore @Inject constructor( } val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == keys.selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, keys.selfSigningKey) + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey) } else { Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.selfSigningKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -390,13 +390,13 @@ internal class RealmCryptoStore @Inject constructor( } // Only for me - if (keys.userSigningKey != null) { + if (userIdentity.userSigningKey != null) { val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == keys.userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, keys.userSigningKey) + if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey) } else { Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.userSigningKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey) signingInfo.setUserSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -1857,8 +1857,8 @@ internal class RealmCryptoStore @Inject constructor( userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) } - userDataToStore.userCrossSigningKeys.forEach { - storeUserCrossSigningKeys(realm, it.key, it.value) + userDataToStore.userIdentities.forEach { + storeUserIdentity(realm, it.key, it.value) } } } From 682bb8bde09fb8f083a7442091b91a0214fe2cf9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 4 Jan 2023 14:06:58 +0100 Subject: [PATCH 35/92] VB - Stop listening if we reach the last received chunk and there is no last sequence number --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index d56f4ad715..9cb894bb58 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -419,7 +419,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return - val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence + val currentSequence = playlist.currentSequence ?: 0 + val lastChunkSequence = mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence ?: 0 + val hasEnded = !isLiveListening && currentSequence >= lastChunkSequence if (hasEnded) { // We'll not receive new chunks anymore so we can stop the live listening stop() From 27d32188bfd4af638af0a43b8044ce1a2a8701d7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 11:04:20 +0100 Subject: [PATCH 36/92] Aggregate data outside of the RealmCryptoStore. --- .../internal/crypto/DefaultCryptoService.kt | 33 ++++++++++++------- .../internal/crypto/store/IMXCryptoStore.kt | 28 +++++++--------- .../crypto/store/db/RealmCryptoStore.kt | 33 +++++++------------ .../room/create/CreateLocalRoomTask.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 7 ++-- .../SyncResponsePostTreatmentAggregator.kt | 5 +++ .../session/sync/handler/CryptoSyncHandler.kt | 5 +-- .../sync/handler/room/RoomSyncHandler.kt | 4 +-- 8 files changed, 60 insertions(+), 57 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 128f06eacb..9f0a780703 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -89,6 +89,7 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask @@ -192,21 +193,21 @@ internal class DefaultCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - fun onStateEvent(roomId: String, event: Event) { + fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } - fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { + fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) { // handle state events if (event.isStateEvent()) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } @@ -384,7 +385,6 @@ internal class DefaultCryptoService @Inject constructor( } } } - cryptoStore.onSyncWillProcess() } private fun internalStart() { @@ -432,8 +432,8 @@ internal class DefaultCryptoService @Inject constructor( * * @param syncResponse the syncResponse */ - fun onSyncCompleted(syncResponse: SyncResponse) { - cryptoStore.onSyncCompleted() + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoStore.storeData(cryptoStoreAggregator) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { @@ -1000,15 +1000,26 @@ internal class DefaultCryptoService @Inject constructor( } } - private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { + private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { if (!event.isStateEvent()) return val eventContent = event.content.toModel() val historyVisibility = eventContent?.historyVisibility if (historyVisibility == null) { - cryptoStore.setShouldShareHistory(roomId, false) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false + } else { + // Store immediately + cryptoStore.setShouldShareHistory(roomId, false) + } } else { - cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) - cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory() + } else { + // Store immediately + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 10158c7a4d..0305f73a7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession @@ -48,21 +49,6 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { - /** - * Notify the store that a sync response treatment is starting. - * Impacted methods: - * - [setShouldShareHistory] - * - [setShouldEncryptForInvitedMembers] - * @See [onSyncCompleted] to notify that the treatment is over. - */ - fun onSyncWillProcess() - - /** - * Notify the store that the sync treatment response is finished. - * The store will save all aggregated data. - */ - fun onSyncCompleted() - /** * @return the device id */ @@ -307,7 +293,11 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean /** - * The data is not stored immediately, this MUST be call during a sync response treatment. + * Sets a boolean flag that will determine whether or not this device should encrypt Events for + * invited members. + * + * @param roomId the room id + * @param shouldEncryptForInvitedMembers The boolean flag */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) @@ -316,7 +306,6 @@ internal interface IMXCryptoStore { /** * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) * will be shared to new user invites. - * The data is not stored immediately, this MUST be call during a sync response treatment. * * @param roomId the room id * @param shouldShareHistory The boolean flag @@ -601,6 +590,11 @@ internal interface IMXCryptoStore { fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + /** + * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. + */ + fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) + /** * Store a bunch of data related to the users. @See [UserDataToStore]. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 1be88249eb..b4368467a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -719,13 +719,17 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - cryptoStoreAggregator?.setShouldEncryptForInvitedMembersData?.put(roomId, shouldEncryptForInvitedMembers) + doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers + } } override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - cryptoStoreAggregator?.setShouldShareHistoryData?.put(roomId, shouldShareHistory) + doRealmTransaction("setShouldShareHistory", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory + } } override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { @@ -1823,37 +1827,24 @@ internal class RealmCryptoStore @Inject constructor( } } - private var cryptoStoreAggregator: CryptoStoreAggregator? = null - override fun onSyncWillProcess() { - if (cryptoStoreAggregator != null) { - Timber.e("cryptoStoreAggregator is not null...") - } - cryptoStoreAggregator = CryptoStoreAggregator() - } - - override fun onSyncCompleted() { - val aggregator = cryptoStoreAggregator ?: return Unit.also { - Timber.e("cryptoStoreAggregator is null...") - } - cryptoStoreAggregator = null - - if (aggregator.isEmpty()) { + override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) { + if (cryptoStoreAggregator.isEmpty()) { return } - doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> + doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm -> // setShouldShareHistory - aggregator.setShouldShareHistoryData.forEach { + cryptoStoreAggregator.setShouldShareHistoryData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } // setShouldEncryptForInvitedMembers - aggregator.setShouldEncryptForInvitedMembersData.forEach { + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } } override fun storeData(userDataToStore: UserDataToStore) { - doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> + doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm -> userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 793c2573be..653069b3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, null) } roomMemberContentsByUser.getOrPut(event.senderId) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 05d50d9595..cb407bb1cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionListeners @@ -92,7 +93,7 @@ internal class SyncResponseHandler @Inject constructor( postTreatmentSyncResponse(syncResponse, isInitialSync) - markCryptoSyncCompleted(syncResponse) + markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator) handlePostSync() @@ -218,10 +219,10 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun markCryptoSyncCompleted(syncResponse: SyncResponse) { + private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { measureTimeMillis { - cryptoSyncHandler.onSyncCompleted(syncResponse) + cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator) }.also { Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index 2b7f936fa8..af05e08da3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.session.sync +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator + internal class SyncResponsePostTreatmentAggregator { // List of RoomId val ephemeralFilesToDelete = mutableListOf() @@ -28,4 +30,7 @@ internal class SyncResponsePostTreatmentAggregator { // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() + + // For the crypto store + val cryptoStoreAggregator = CryptoStoreAggregator() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index 551db52dbd..7224b0c29c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.session.sync.ProgressReporter @@ -85,8 +86,8 @@ internal class CryptoSyncHandler @Inject constructor( } } - fun onSyncCompleted(syncResponse: SyncResponse) { - cryptoService.onSyncCompleted(syncResponse) + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator) } /** 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 ccc3820bb6..5e4886ce1e 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 @@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor( root = eventEntity } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator) roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator) } } @@ -495,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor( } } // Give info to crypto module - cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) + cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator) // Try to remove local echo event.unsignedData?.transactionId?.also { txId -> From dbf3b763311ea0faae4df39a178ec584f5537f95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 11:54:19 +0100 Subject: [PATCH 37/92] Update doc. --- .../matrix/android/sdk/internal/crypto/DefaultCryptoService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 9f0a780703..50497e3a27 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -431,6 +431,7 @@ internal class DefaultCryptoService @Inject constructor( * A sync response has been received. * * @param syncResponse the syncResponse + * @param cryptoStoreAggregator data aggregated during the sync response treatment to store */ fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { cryptoStore.storeData(cryptoStoreAggregator) From 0d2fb8e3d089bce9c47f3ace8b170be6c661ffe5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Jan 2023 10:17:35 +0100 Subject: [PATCH 38/92] Lint: fix KotlinNullnessAnnotation warning --- .../java/org/matrix/android/sdk/internal/di/SerializeNulls.kt | 2 -- .../internal/network/interceptors/FormattedJsonHttpLogger.kt | 3 +-- .../android/sdk/internal/network/parsing/CheckNumberType.kt | 3 --- tools/lint/lint.xml | 1 + .../java/im/vector/app/core/resources/StringArrayProvider.kt | 2 -- .../main/java/im/vector/app/core/resources/StringProvider.kt | 4 ---- 6 files changed, 2 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt index 9bd197e42e..f89221b627 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.di -import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonQualifier import com.squareup.moshi.Moshi @@ -28,7 +27,6 @@ import java.lang.reflect.Type internal annotation class SerializeNulls { companion object { val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory { - @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java) ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 4e0525536c..334a8c5076 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.network.interceptors -import androidx.annotation.NonNull import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException @@ -38,7 +37,7 @@ internal class FormattedJsonHttpLogger( * @param message */ @Synchronized - override fun log(@NonNull message: String) { + override fun log(message: String) { Timber.v(message) // Try to log formatted Json only if there is a chance that [message] contains Json. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt index 8b54978279..6c28b9fcce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.network.parsing -import androidx.annotation.Nullable import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter @@ -32,14 +31,12 @@ internal interface CheckNumberType { companion object { val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory { - @Nullable override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { if (type !== Any::class.java) { return null } val delegate: JsonAdapter = moshi.nextAdapter(this, Any::class.java, emptySet()) return object : JsonAdapter() { - @Nullable @Throws(IOException::class) override fun fromJson(reader: JsonReader): Any? { return if (reader.peek() !== JsonReader.Token.NUMBER) { diff --git a/tools/lint/lint.xml b/tools/lint/lint.xml index 3d3b073749..dbe30f2267 100644 --- a/tools/lint/lint.xml +++ b/tools/lint/lint.xml @@ -77,6 +77,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt index 9ed3c02ba4..3c057e0635 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringArrayProvider.kt @@ -18,7 +18,6 @@ package im.vector.app.core.resources import android.content.res.Resources import androidx.annotation.ArrayRes -import androidx.annotation.NonNull import javax.inject.Inject class StringArrayProvider @Inject constructor(private val resources: Resources) { @@ -31,7 +30,6 @@ class StringArrayProvider @Inject constructor(private val resources: Resources) * @return The string array associated with the resource, stripped of styled * text information. */ - @NonNull fun getStringArray(@ArrayRes resId: Int): Array { return resources.getStringArray(resId) } diff --git a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt index 7e322daaae..e5f48f8be0 100644 --- a/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/StringProvider.kt @@ -17,7 +17,6 @@ package im.vector.app.core.resources import android.content.res.Resources -import androidx.annotation.NonNull import androidx.annotation.PluralsRes import androidx.annotation.StringRes import javax.inject.Inject @@ -32,7 +31,6 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, stripped of styled * text information. */ - @NonNull fun getString(@StringRes resId: Int): String { return resources.getString(resId) } @@ -48,12 +46,10 @@ class StringProvider @Inject constructor(private val resources: Resources) { * @return The string data associated with the resource, formatted and * stripped of styled text information. */ - @NonNull fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { return resources.getString(resId, *formatArgs) } - @NonNull fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resources.getQuantityString(resId, quantity, *formatArgs) } From 87e661e3b5d895990648f5cc0e71e60d08d10982 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 5 Jan 2023 14:36:22 +0100 Subject: [PATCH 39/92] Add changelog file --- changelog.d/7899.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7899.bugfix diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix new file mode 100644 index 0000000000..d95af29d8d --- /dev/null +++ b/changelog.d/7899.bugfix @@ -0,0 +1 @@ +[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number From d60403545c4f323f7067bef06776660cdcf4b4d3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:09:41 +0100 Subject: [PATCH 40/92] Renaming of filter enum --- .../app/features/roomprofile/polls/GetPollsUseCase.kt | 6 +++--- .../app/features/roomprofile/polls/RoomPollsAction.kt | 2 +- .../polls/{RoomPollsFilter.kt => RoomPollsFilterType.kt} | 2 +- .../app/features/roomprofile/polls/RoomPollsViewModel.kt | 2 +- .../roomprofile/polls/active/RoomActivePollsFragment.kt | 4 ++-- .../features/roomprofile/polls/RoomPollsViewModelTest.kt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{RoomPollsFilter.kt => RoomPollsFilterType.kt} (95%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index fa8c6d0aa6..d35d192e04 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -24,11 +24,11 @@ import javax.inject.Inject class GetPollsUseCase @Inject constructor() { - fun execute(filter: RoomPollsFilter): Flow> { + fun execute(filter: RoomPollsFilterType): Flow> { // TODO unmock and add unit tests return when (filter) { - RoomPollsFilter.ACTIVE -> getActivePolls() - RoomPollsFilter.ENDED -> emptyFlow() + RoomPollsFilterType.ACTIVE -> getActivePolls() + RoomPollsFilterType.ENDED -> emptyFlow() }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 27753b6d16..5f074bdd6f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -19,5 +19,5 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { - data class SetFilter(val filter: RoomPollsFilter) : RoomPollsAction + data class SetFilter(val filter: RoomPollsFilterType) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt index 68ebb13f7d..39f1163536 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.polls -enum class RoomPollsFilter { +enum class RoomPollsFilterType { ACTIVE, ENDED, } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 7def7a508d..7bc06894fa 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -54,7 +54,7 @@ class RoomPollsViewModel @AssistedInject constructor( super.onCleared() } - private fun handleSetFilter(filter: RoomPollsFilter) { + private fun handleSetFilter(filter: RoomPollsFilterType) { pollsCollectionJob?.cancel() pollsCollectionJob = getPollsUseCase.execute(filter) .onEach { setState { copy(polls = it) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 4cc318edf9..61c7e961bd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -31,7 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsAction -import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.RoomPollsFilterType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import timber.log.Timber import javax.inject.Inject @@ -73,7 +73,7 @@ class RoomActivePollsFragment : override fun onResume() { super.onResume() - viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilter.ACTIVE)) + viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilterType.ACTIVE)) } override fun invalidate() = withState(viewModel) { viewState -> diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 54b2a60d55..0dce2dd6e0 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -47,7 +47,7 @@ class RoomPollsViewModelTest { @Test fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { // Given - val filter = RoomPollsFilter.ACTIVE + val filter = RoomPollsFilterType.ACTIVE val action = RoomPollsAction.SetFilter(filter = filter) val polls = listOf(givenAPollSummary()) every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) From ff9e78be42c500fd3a0985cadb9db67be8c54df3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:20:20 +0100 Subject: [PATCH 41/92] Use classical for loop instead of forEach --- .../roomprofile/polls/active/RoomActivePollsController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index dc14ec366d..7a7c818693 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -38,7 +38,7 @@ class RoomActivePollsController @Inject constructor( } val host = this - data.forEach { poll -> + for (poll in data) { activePollItem { id(poll.id) formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) From 2dab6ed052912e6296b643d15b30c69ce0df7516 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:27:11 +0100 Subject: [PATCH 42/92] Fix horizontal margin of tabs --- vector/src/main/res/layout/fragment_room_polls.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index dcaf483251..396d6fd8c5 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -24,7 +24,7 @@ android:id="@+id/roomPollsTabs" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="10dp" + android:layout_marginHorizontal="4dp" android:layout_marginTop="20dp" android:background="?android:colorBackground" app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager" From 7fc9705f3a92d392b0ce8855ecd79e6abb32cc40 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 16:37:06 +0100 Subject: [PATCH 43/92] Adding importantForAccessibility attribute to icon --- vector/src/main/res/layout/item_poll.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 05e9b3a62a..956ecf9b3c 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -22,6 +22,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginTop="12dp" + android:importantForAccessibility="no" android:src="@drawable/ic_attachment_poll" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/pollActiveDate" From 0b535910d649d5c9dbe3777f15971aabb4cc973c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:50:32 +0100 Subject: [PATCH 44/92] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip index 4dc55708be..e1187ee1e7 100644 --- a/changelog.d/7864.wip +++ b/changelog.d/7864.wip @@ -1 +1,2 @@ [Poll] Render active polls list of a room +[Poll] Render past polls list of a room From cb45056c1a311ec652d72a198c089abb66f78669 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:28:57 +0100 Subject: [PATCH 45/92] Mutualizing list fragments and add ended polls tab --- .../src/main/res/values/strings.xml | 2 + .../roomprofile/polls/GetPollsUseCase.kt | 9 +- .../features/roomprofile/polls/PollSummary.kt | 6 ++ .../roomprofile/polls/RoomPollsAction.kt | 4 +- .../roomprofile/polls/RoomPollsFragment.kt | 1 + .../polls/RoomPollsPagerAdapter.kt | 8 +- .../roomprofile/polls/RoomPollsViewModel.kt | 25 ++---- .../polls/active/RoomActivePollsFragment.kt | 70 ++------------- .../polls/ended/RoomEndedPollsFragment.kt | 34 +++++++ .../RoomPollItem.kt} | 4 +- .../RoomPollsController.kt} | 43 ++++++--- .../polls/list/RoomPollsListFragment.kt | 90 +++++++++++++++++++ 12 files changed, 190 insertions(+), 106 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{active/ActivePollItem.kt => list/RoomPollItem.kt} (90%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{active/RoomActivePollsController.kt => list/RoomPollsController.kt} (50%) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 43507e60ce..6120398566 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3193,6 +3193,8 @@ Results are only revealed when you end the poll Active polls There are no active polls in this room + Past polls + There are no past polls in this room Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index d35d192e04..06915c7b6a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -17,19 +17,16 @@ package im.vector.app.features.roomprofile.polls import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import javax.inject.Inject class GetPollsUseCase @Inject constructor() { - fun execute(filter: RoomPollsFilterType): Flow> { + fun execute(): Flow> { // TODO unmock and add unit tests - return when (filter) { - RoomPollsFilterType.ACTIVE -> getActivePolls() - RoomPollsFilterType.ENDED -> emptyFlow() - }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } + return getActivePolls() + .map { it.sortedByDescending { poll -> poll.creationTimestamp } } } private fun getActivePolls(): Flow> { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt index 3eb45c6144..939ee3ffa0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -22,4 +22,10 @@ sealed interface PollSummary { val creationTimestamp: Long, val title: String, ) : PollSummary + + data class EndedPoll( + val id: String, + val creationTimestamp: Long, + val title: String, + ) : PollSummary } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 5f074bdd6f..c18142a306 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -18,6 +18,4 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction -sealed interface RoomPollsAction : VectorViewModelAction { - data class SetFilter(val filter: RoomPollsFilterType) : RoomPollsAction -} +sealed interface RoomPollsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 5c150f4391..95aa5d0d95 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -67,6 +67,7 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { 0 -> tab.text = getString(R.string.room_polls_active) + 1 -> tab.text = getString(R.string.room_polls_ended) } }.also { it.attach() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt index 5472782079..2dc6fd4369 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt @@ -19,15 +19,19 @@ package im.vector.app.features.roomprofile.polls import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment +import im.vector.app.features.roomprofile.polls.ended.RoomEndedPollsFragment class RoomPollsPagerAdapter( private val fragment: Fragment ) : FragmentStateAdapter(fragment) { - override fun getItemCount() = 1 + override fun getItemCount() = 2 override fun createFragment(position: Int): Fragment { - return instantiateFragment(RoomActivePollsFragment::class.java.name) + return when (position) { + 0 -> instantiateFragment(RoomActivePollsFragment::class.java.name) + else -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + } } private fun instantiateFragment(fragmentName: String): Fragment { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 7bc06894fa..95cb4717ca 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.polls -import androidx.annotation.VisibleForTesting import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -24,7 +23,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -40,24 +38,17 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - @VisibleForTesting - var pollsCollectionJob: Job? = null - - override fun handle(action: RoomPollsAction) { - when (action) { - is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) - } + init { + observePolls() } - override fun onCleared() { - pollsCollectionJob = null - super.onCleared() - } - - private fun handleSetFilter(filter: RoomPollsFilterType) { - pollsCollectionJob?.cancel() - pollsCollectionJob = getPollsUseCase.execute(filter) + private fun observePolls() { + getPollsUseCase.execute() .onEach { setState { copy(polls = it) } } .launchIn(viewModelScope) } + + override fun handle(action: RoomPollsAction) { + // do nothing for now + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 61c7e961bd..dc735c79be 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -16,77 +16,19 @@ package im.vector.app.features.roomprofile.polls.active -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -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.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentRoomPollsListBinding -import im.vector.app.features.roomprofile.polls.PollSummary -import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsFilterType -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel -import timber.log.Timber -import javax.inject.Inject +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment @AndroidEntryPoint -class RoomActivePollsFragment : - VectorBaseFragment(), - RoomActivePollsController.Listener { +class RoomActivePollsFragment : RoomPollsListFragment() { - @Inject - lateinit var roomActivePollsController: RoomActivePollsController - - private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { - return FragmentRoomPollsListBinding.inflate(inflater, container, false) + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_active_no_item) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupList() - } - - private fun setupList() { - roomActivePollsController.listener = this - views.roomPollsList.configureWith(roomActivePollsController) - views.roomPollsEmptyTitle.text = getString(R.string.room_polls_active_no_item) - } - - override fun onDestroyView() { - cleanUpList() - super.onDestroyView() - } - - private fun cleanUpList() { - views.roomPollsList.cleanup() - roomActivePollsController.listener = null - } - - override fun onResume() { - super.onResume() - viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilterType.ACTIVE)) - } - - override fun invalidate() = withState(viewModel) { viewState -> - renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) - } - - private fun renderList(polls: List) { - roomActivePollsController.setData(polls) - views.roomPollsEmptyTitle.isVisible = polls.isEmpty() - } - - override fun onPollClicked(pollId: String) { - // TODO navigate to details - Timber.d("poll with id $pollId clicked") + override fun getRoomPollsFilter(): RoomPollsFilterType { + return RoomPollsFilterType.ACTIVE } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt new file mode 100644 index 0000000000..bad1a4e2da --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.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 im.vector.app.features.roomprofile.polls.ended + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment + +@AndroidEntryPoint +class RoomEndedPollsFragment : RoomPollsListFragment() { + + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_ended_no_item) + } + + override fun getRoomPollsFilter(): RoomPollsFilter { + return RoomPollsFilter.ENDED + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt similarity index 90% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt index 35b1ecd6e1..ac2b9cf3c0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.active +package im.vector.app.features.roomprofile.polls.list import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute @@ -26,7 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick @EpoxyModelClass -abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll) { +abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { @EpoxyAttribute lateinit var formattedDate: String diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt similarity index 50% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt index 7a7c818693..e24241f0af 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.active +package im.vector.app.features.roomprofile.polls.list import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.core.date.DateFormatKind @@ -22,9 +22,9 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.features.roomprofile.polls.PollSummary import javax.inject.Inject -class RoomActivePollsController @Inject constructor( +class RoomPollsController @Inject constructor( val dateFormatter: VectorDateFormatter, -) : TypedEpoxyController>() { +) : TypedEpoxyController>() { interface Listener { fun onPollClicked(pollId: String) @@ -32,20 +32,39 @@ class RoomActivePollsController @Inject constructor( var listener: Listener? = null - override fun buildModels(data: List?) { + override fun buildModels(data: List?) { if (data.isNullOrEmpty()) { return } - val host = this for (poll in data) { - activePollItem { - id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) - title(poll.title) - clickListener { - host.listener?.onPollClicked(poll.id) - } + when (poll) { + is PollSummary.ActivePoll -> buildActivePollItem(poll) + is PollSummary.EndedPoll -> buildEndedPollItem(poll) + } + } + } + + private fun buildActivePollItem(poll: PollSummary.ActivePoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } + + private fun buildEndedPollItem(poll: PollSummary.EndedPoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt new file mode 100644 index 0000000000..f408f1c781 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt @@ -0,0 +1,90 @@ +/* + * 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.roomprofile.polls.list + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.PollSummary +import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import timber.log.Timber +import javax.inject.Inject + +abstract class RoomPollsListFragment : + VectorBaseFragment(), + RoomPollsController.Listener { + + @Inject + lateinit var roomPollsController: RoomPollsController + + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { + return FragmentRoomPollsListBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupList() + } + + abstract fun getEmptyListTitle(): String + + abstract fun getRoomPollsFilter(): RoomPollsFilter + + private fun setupList() { + roomPollsController.listener = this + views.roomPollsList.configureWith(roomPollsController) + views.roomPollsEmptyTitle.text = getEmptyListTitle() + } + + override fun onDestroyView() { + cleanUpList() + super.onDestroyView() + } + + private fun cleanUpList() { + views.roomPollsList.cleanup() + roomPollsController.listener = null + } + + override fun invalidate() = withState(viewModel) { viewState -> + when (getRoomPollsFilter()) { + RoomPollsFilter.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsFilter.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) + } + } + + private fun renderList(polls: List) { + roomPollsController.setData(polls) + views.roomPollsEmptyTitle.isVisible = polls.isEmpty() + } + + override fun onPollClicked(pollId: String) { + // TODO navigate to details + Timber.d("poll with id $pollId clicked") + } +} From 740591cd38a01d8bf469cac3d78cf162e4a71c89 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:31:23 +0100 Subject: [PATCH 46/92] Updating unit tests --- .../roomprofile/polls/RoomPollsViewModelTest.kt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 0dce2dd6e0..9cca32c5e6 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -23,7 +23,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.flowOf -import org.amshove.kluent.shouldNotBeNull import org.junit.Rule import org.junit.Test @@ -45,28 +44,22 @@ class RoomPollsViewModelTest { } @Test - fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { + fun `given viewModel when created then polls list is observed and viewState is updated`() { // Given - val filter = RoomPollsFilterType.ACTIVE - val action = RoomPollsAction.SetFilter(filter = filter) val polls = listOf(givenAPollSummary()) - every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) - val viewModel = createViewModel() + every { fakeGetPollsUseCase.execute() } returns flowOf(polls) val expectedViewState = initialState.copy(polls = polls) // When + val viewModel = createViewModel() val viewModelTest = viewModel.test() - viewModel.pollsCollectionJob = null - viewModel.handle(action) // Then viewModelTest .assertLatestState(expectedViewState) .finish() - viewModel.pollsCollectionJob.shouldNotBeNull() verify { - viewModel.pollsCollectionJob?.cancel() - fakeGetPollsUseCase.execute(filter) + fakeGetPollsUseCase.execute() } } From cf82486efa20613b7130399928c094eb031a790b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:38:55 +0100 Subject: [PATCH 47/92] Adding mocked data for ended polls --- .../roomprofile/polls/GetPollsUseCase.kt | 79 ++++++++++++------- .../features/roomprofile/polls/PollSummary.kt | 16 ++-- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index 06915c7b6a..7346f8769c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -25,38 +25,59 @@ class GetPollsUseCase @Inject constructor() { fun execute(): Flow> { // TODO unmock and add unit tests - return getActivePolls() + return flowOf(getActivePolls() + getEndedPolls()) .map { it.sortedByDescending { poll -> poll.creationTimestamp } } } - private fun getActivePolls(): Flow> { - return flowOf( - listOf( - PollSummary.ActivePoll( - id = "id1", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?" - ), - PollSummary.ActivePoll( - id = "id2", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Which sport should the pupils do this year?" - ), - PollSummary.ActivePoll( - id = "id3", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?" - ), - PollSummary.ActivePoll( - id = "id4", - // 2022/06/22 UTC+1 - creationTimestamp = 1655848800000, - title = "What film should we show at the end of the year party?" - ), - ) + private fun getActivePolls(): List { + return listOf( + PollSummary.ActivePoll( + id = "id1", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.ActivePoll( + id = "id2", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Which sport should the pupils do this year?" + ), + PollSummary.ActivePoll( + id = "id3", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), + PollSummary.ActivePoll( + id = "id4", + // 2022/06/22 UTC+1 + creationTimestamp = 1655848800000, + title = "What film should we show at the end of the year party?" + ), + ) + } + + private fun getEndedPolls(): List { + return listOf( + PollSummary.EndedPoll( + id = "id1-ended", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.EndedPoll( + id = "id2-ended", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Where should we do the offsite?" + ), + PollSummary.EndedPoll( + id = "id3-ended", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt index 939ee3ffa0..12ac97dc02 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -17,15 +17,19 @@ package im.vector.app.features.roomprofile.polls sealed interface PollSummary { + val id: String + val creationTimestamp: Long + val title: String + data class ActivePoll( - val id: String, - val creationTimestamp: Long, - val title: String, + override val id: String, + override val creationTimestamp: Long, + override val title: String, ) : PollSummary data class EndedPoll( - val id: String, - val creationTimestamp: Long, - val title: String, + override val id: String, + override val creationTimestamp: Long, + override val title: String, ) : PollSummary } From 3deae1101c317375dbf1f14ddbcde8e40fa5a6c9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:32:41 +0100 Subject: [PATCH 48/92] Adding extra data for ended poll --- .../roomprofile/polls/GetPollsUseCase.kt | 37 +++++++++++++++++-- .../features/roomprofile/polls/PollSummary.kt | 4 ++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index 7346f8769c..6f2a757ed7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.polls +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -64,19 +65,49 @@ class GetPollsUseCase @Inject constructor() { id = "id1-ended", // 2022/06/28 UTC+1 creationTimestamp = 1656367200000, - title = "Which charity would you like to support?" + title = "Which charity would you like to support?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Cancer research", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) + ), ), PollSummary.EndedPoll( id = "id2-ended", // 2022/06/26 UTC+1 creationTimestamp = 1656194400000, - title = "Where should we do the offsite?" + title = "Where should we do the offsite?", + totalVotes = 92, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Hawaii", + voteCount = 43, + votePercentage = 43 / 92.0, + isWinner = true, + ) + ), ), PollSummary.EndedPoll( id = "id3-ended", // 2022/06/24 UTC+1 creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?" + title = "What type of food should we have at the party?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Brazilian", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) + ), ), ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt index 12ac97dc02..f24ac8b8a6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -16,6 +16,8 @@ package im.vector.app.features.roomprofile.polls +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + sealed interface PollSummary { val id: String val creationTimestamp: Long @@ -31,5 +33,7 @@ sealed interface PollSummary { override val id: String, override val creationTimestamp: Long, override val title: String, + val totalVotes: Int, + val winnerOptions: List, ) : PollSummary } From 1cc26449f3f9145abe79e105a9635aa1f4152dde Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:12:34 +0100 Subject: [PATCH 49/92] Renaming some ui fields --- .../features/roomprofile/polls/list/RoomPollItem.kt | 4 ++-- vector/src/main/res/layout/item_poll.xml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt index ac2b9cf3c0..633162f6eb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -45,7 +45,7 @@ abstract class RoomPollItem : VectorEpoxyModel(R.layout.ite } class Holder : VectorEpoxyHolder() { - val date by bind(R.id.pollActiveDate) - val title by bind(R.id.pollActiveTitle) + val date by bind(R.id.pollDate) + val title by bind(R.id.pollTitle) } } diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 956ecf9b3c..8e9fa54a0f 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -7,7 +7,7 @@ android:foreground="?selectableItemBackground"> From 05363dc8ca26609ff41f746869a720ccb7da6135 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:30:57 +0100 Subject: [PATCH 50/92] Adding winner option views for ended poll items --- .../features/roomprofile/polls/list/RoomPollItem.kt | 13 +++++++++++++ .../roomprofile/polls/list/RoomPollsController.kt | 1 + vector/src/main/res/layout/item_poll.xml | 12 ++++++++++++ 3 files changed, 26 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt index 633162f6eb..fd4e36159e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.polls.list +import android.widget.LinearLayout import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -24,6 +25,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.features.home.room.detail.timeline.item.PollOptionView +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState @EpoxyModelClass abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { @@ -34,6 +37,9 @@ abstract class RoomPollItem : VectorEpoxyModel(R.layout.ite @EpoxyAttribute lateinit var title: String + @EpoxyAttribute + var winnerOptions: List = emptyList() + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null @@ -42,10 +48,17 @@ abstract class RoomPollItem : VectorEpoxyModel(R.layout.ite holder.view.onClick(clickListener) holder.date.text = formattedDate holder.title.text = title + holder.winnerOptions.removeAllViews() + for (winnerOption in winnerOptions) { + val optionView = PollOptionView(holder.view.context) + holder.winnerOptions.addView(optionView) + optionView.render(winnerOption) + } } class Holder : VectorEpoxyHolder() { val date by bind(R.id.pollDate) val title by bind(R.id.pollTitle) + val winnerOptions by bind(R.id.pollWinnerOptionsContainer) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt index e24241f0af..c6288aac8b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt @@ -63,6 +63,7 @@ class RoomPollsController @Inject constructor( id(poll.id) formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) title(poll.title) + winnerOptions(poll.winnerOptions) clickListener { host.listener?.onPollClicked(poll.id) } diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 8e9fa54a0f..9563f3d52a 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -42,4 +42,16 @@ app:layout_constraintTop_toBottomOf="@id/pollDate" tools:text="Which sport should the pupils do this year?" /> + + From a5d076a28a2c82483cdaea9114f1e0cc0cc561da Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 4 Jan 2023 10:49:07 +0100 Subject: [PATCH 51/92] Adding total votes status for ended poll items --- .../features/roomprofile/polls/list/RoomPollItem.kt | 8 ++++++++ .../roomprofile/polls/list/RoomPollsController.kt | 4 ++++ vector/src/main/res/layout/item_poll.xml | 12 ++++++++++++ 3 files changed, 24 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt index fd4e36159e..da00fedddb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile.polls.list import android.widget.LinearLayout import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -25,6 +26,7 @@ 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.extensions.setTextOrHide import im.vector.app.features.home.room.detail.timeline.item.PollOptionView import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState @@ -40,6 +42,9 @@ abstract class RoomPollItem : VectorEpoxyModel(R.layout.ite @EpoxyAttribute var winnerOptions: List = emptyList() + @EpoxyAttribute + var totalVotesStatus: String? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null @@ -49,16 +54,19 @@ abstract class RoomPollItem : VectorEpoxyModel(R.layout.ite holder.date.text = formattedDate holder.title.text = title holder.winnerOptions.removeAllViews() + holder.winnerOptions.isVisible = winnerOptions.isNotEmpty() for (winnerOption in winnerOptions) { val optionView = PollOptionView(holder.view.context) holder.winnerOptions.addView(optionView) optionView.render(winnerOption) } + holder.totalVotes.setTextOrHide(totalVotesStatus) } class Holder : VectorEpoxyHolder() { val date by bind(R.id.pollDate) val title by bind(R.id.pollTitle) val winnerOptions by bind(R.id.pollWinnerOptionsContainer) + val totalVotes by bind(R.id.pollTotalVotes) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt index c6288aac8b..f0e3b6b9a4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt @@ -17,13 +17,16 @@ package im.vector.app.features.roomprofile.polls.list import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider import im.vector.app.features.roomprofile.polls.PollSummary import javax.inject.Inject class RoomPollsController @Inject constructor( val dateFormatter: VectorDateFormatter, + val stringProvider: StringProvider, ) : TypedEpoxyController>() { interface Listener { @@ -64,6 +67,7 @@ class RoomPollsController @Inject constructor( formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) title(poll.title) winnerOptions(poll.winnerOptions) + totalVotesStatus(host.stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, poll.totalVotes, poll.totalVotes)) clickListener { host.listener?.onPollClicked(poll.id) } diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 9563f3d52a..17f3b5abf5 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -54,4 +54,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/pollTitle" /> + + From 9b5fda2689531063847707804100be2576990afa Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:45:35 +0100 Subject: [PATCH 52/92] Fix after rebase --- .../roomprofile/polls/ended/RoomEndedPollsFragment.kt | 6 +++--- .../roomprofile/polls/list/RoomPollsListFragment.kt | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt index bad1a4e2da..add7096409 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.polls.ended import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.RoomPollsFilterType import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment @AndroidEntryPoint @@ -28,7 +28,7 @@ class RoomEndedPollsFragment : RoomPollsListFragment() { return getString(R.string.room_polls_ended_no_item) } - override fun getRoomPollsFilter(): RoomPollsFilter { - return RoomPollsFilter.ENDED + override fun getRoomPollsFilter(): RoomPollsFilterType { + return RoomPollsFilterType.ENDED } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt index f408f1c781..f6aa59f447 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt @@ -28,7 +28,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary -import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.RoomPollsFilterType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import timber.log.Timber import javax.inject.Inject @@ -53,7 +53,7 @@ abstract class RoomPollsListFragment : abstract fun getEmptyListTitle(): String - abstract fun getRoomPollsFilter(): RoomPollsFilter + abstract fun getRoomPollsFilter(): RoomPollsFilterType private fun setupList() { roomPollsController.listener = this @@ -73,8 +73,8 @@ abstract class RoomPollsListFragment : override fun invalidate() = withState(viewModel) { viewState -> when (getRoomPollsFilter()) { - RoomPollsFilter.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) - RoomPollsFilter.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) + RoomPollsFilterType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsFilterType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) } } From b8da53b3bb4a7e62e0fa20aeaab425c4782742ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 11:56:11 +0000 Subject: [PATCH 53/92] Bump checker from 3.27.0 to 3.29.0 (#7903) Bumps [checker](https://github.com/typetools/checker-framework) from 3.27.0 to 3.29.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.29.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker 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> --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 83af7ecc04..91d2a8c46a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -308,7 +308,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.27.0" + implementation "org.checkerframework:checker:3.29.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner From 85cfa433d9e15362c0b907d19e253c01cff665c5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 6 Jan 2023 14:13:58 +0100 Subject: [PATCH 54/92] Using ordinal of enum to render tabs --- .../features/roomprofile/polls/RoomPollsFragment.kt | 4 ++-- .../roomprofile/polls/RoomPollsPagerAdapter.kt | 7 ++++--- .../polls/{RoomPollsFilterType.kt => RoomPollsType.kt} | 2 +- .../polls/active/RoomActivePollsFragment.kt | 6 +++--- .../roomprofile/polls/ended/RoomEndedPollsFragment.kt | 6 +++--- .../roomprofile/polls/list/RoomPollsListFragment.kt | 10 +++++----- 6 files changed, 18 insertions(+), 17 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{RoomPollsFilterType.kt => RoomPollsType.kt} (95%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 95aa5d0d95..9f7e704135 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -66,8 +66,8 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - 0 -> tab.text = getString(R.string.room_polls_active) - 1 -> tab.text = getString(R.string.room_polls_ended) + RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) + RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) } }.also { it.attach() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt index 2dc6fd4369..c60fc5de27 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt @@ -25,12 +25,13 @@ class RoomPollsPagerAdapter( private val fragment: Fragment ) : FragmentStateAdapter(fragment) { - override fun getItemCount() = 2 + override fun getItemCount() = RoomPollsType.values().size override fun createFragment(position: Int): Fragment { return when (position) { - 0 -> instantiateFragment(RoomActivePollsFragment::class.java.name) - else -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + RoomPollsType.ACTIVE.ordinal -> instantiateFragment(RoomActivePollsFragment::class.java.name) + RoomPollsType.ENDED.ordinal -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + else -> throw IllegalArgumentException("position should be between 0 and ${itemCount - 1}, while it was $position") } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt index 39f1163536..134ef9a195 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.polls -enum class RoomPollsFilterType { +enum class RoomPollsType { ACTIVE, ENDED, } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index dc735c79be..1c6a03c480 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.polls.active import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.features.roomprofile.polls.RoomPollsFilterType +import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment @AndroidEntryPoint @@ -28,7 +28,7 @@ class RoomActivePollsFragment : RoomPollsListFragment() { return getString(R.string.room_polls_active_no_item) } - override fun getRoomPollsFilter(): RoomPollsFilterType { - return RoomPollsFilterType.ACTIVE + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ACTIVE } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt index add7096409..8dd0cadadf 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.polls.ended import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.features.roomprofile.polls.RoomPollsFilterType +import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment @AndroidEntryPoint @@ -28,7 +28,7 @@ class RoomEndedPollsFragment : RoomPollsListFragment() { return getString(R.string.room_polls_ended_no_item) } - override fun getRoomPollsFilter(): RoomPollsFilterType { - return RoomPollsFilterType.ENDED + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ENDED } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt index f6aa59f447..0d97bd8dcb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt @@ -28,7 +28,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary -import im.vector.app.features.roomprofile.polls.RoomPollsFilterType +import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import timber.log.Timber import javax.inject.Inject @@ -53,7 +53,7 @@ abstract class RoomPollsListFragment : abstract fun getEmptyListTitle(): String - abstract fun getRoomPollsFilter(): RoomPollsFilterType + abstract fun getRoomPollsType(): RoomPollsType private fun setupList() { roomPollsController.listener = this @@ -72,9 +72,9 @@ abstract class RoomPollsListFragment : } override fun invalidate() = withState(viewModel) { viewState -> - when (getRoomPollsFilter()) { - RoomPollsFilterType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) - RoomPollsFilterType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) + when (getRoomPollsType()) { + RoomPollsType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) } } From 0dd1abb9262a9ecf28ac85c99958e238d1459a95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Dec 2022 13:02:02 +0100 Subject: [PATCH 55/92] Rename method --- .../app/features/crypto/quads/SharedSecureStorageActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d393636a8e..aea87beea9 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 @@ -59,7 +59,7 @@ class SharedSecureStorageActivity : views.toolbar.visibility = View.GONE - viewModel.observeViewEvents { observeViewEvents(it) } + viewModel.observeViewEvents { onViewEvents(it) } viewModel.onEach { renderState(it) } } @@ -85,7 +85,7 @@ class SharedSecureStorageActivity : showFragment(fragment) } - private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { + private fun onViewEvents(it: SharedSecureStorageViewEvent) { when (it) { is SharedSecureStorageViewEvent.Dismiss -> { finish() From 9c79d234440310bf41e4964c78dc48e8bbb89c15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Dec 2022 21:02:33 +0100 Subject: [PATCH 56/92] Ensure event are not sent if the lifecycle state is not RESUMED --- .../app/core/platform/VectorBaseActivity.kt | 22 ++++++++++++------- .../VectorBaseBottomSheetDialogFragment.kt | 21 ++++++++++++------ .../app/core/platform/VectorBaseFragment.kt | 22 ++++++++++++------- .../im/vector/app/features/MainActivity.kt | 8 +++---- .../media/VectorAttachmentViewerActivity.kt | 16 +++++++++----- .../settings/VectorSettingsBaseFragment.kt | 22 +++++++++++++------ 6 files changed, 70 insertions(+), 41 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 4e5116eda9..a87eb92b13 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 @@ -41,6 +41,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView @@ -91,6 +92,7 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError @@ -123,14 +125,18 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected val viewModelProvider get() = ViewModelProvider(this, viewModelFactory) - fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - hideWaitingView() - observer(it) - } - .launchIn(lifecycleScope) + fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents.stream() + .collect { + hideWaitingView() + observer(it) + } + } + } } var toolbar: ToolbarConfig? = null diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index ec6f3288f8..ad7a86c899 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -26,8 +26,10 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.annotation.FloatRange +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -43,6 +45,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.github.hyuwah.draggableviewlib.Utils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -199,12 +202,16 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents.stream() + .collect { + observer(it) + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 8fe2d33f6a..9f79db9c66 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -34,6 +34,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread @@ -53,6 +54,7 @@ import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -272,14 +274,18 @@ abstract class VectorBaseFragment : Fragment(), MavericksView * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - dismissLoadingDialog() - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents.stream() + .collect { + dismissLoadingDialog() + observer(it) + } + } + } } /* ========================================================================================== 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 8ce375122e..cffb1577cf 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -55,8 +55,6 @@ import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -142,9 +140,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity startAppViewModel.onEach { renderState(it) } - startAppViewModel.viewEvents.stream() - .onEach(::handleViewEvents) - .launchIn(lifecycleScope) + startAppViewModel.observeViewEvents { + handleViewEvents(it) + } startAppViewModel.handle(StartAppAction.StartApp) } 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 089fdcebd4..9f9488e35d 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 @@ -29,7 +29,9 @@ import androidx.core.transition.addListener import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.transition.Transition import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint @@ -50,8 +52,6 @@ import im.vector.lib.attachmentviewer.AttachmentViewerActivity import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat import im.vector.lib.core.utils.compat.getParcelableExtraCompat import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -239,10 +239,14 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } private fun observeViewEvents() { - viewModel.viewEvents - .stream() - .onEach(::handleViewEvents) - .launchIn(lifecycleScope) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel + .viewEvents + .stream() + .collect(::handleViewEvents) + } + } } private fun handleViewEvents(event: VectorAttachmentViewerViewEvents) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 38ba949a49..6299d8962d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -20,7 +20,9 @@ import android.content.Context import android.os.Bundle import android.view.View import androidx.annotation.CallSuper +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceFragmentCompat import com.airbnb.mvrx.MavericksView import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -35,6 +37,7 @@ import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.MobileScreen import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -66,13 +69,18 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick * ViewEvents * ========================================================================================== */ - protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + protected fun VectorViewModel<*, *, T>.observeViewEvents( + observer: (T) -> Unit, + ) { + lifecycleScope.launch { + repeatOnLifecycle(state) { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents.stream() + .collect { + observer(it) + } + } + } } /* ========================================================================================== From 71bd4f457a8093683c6b9d046352e032c92d21eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Dec 2022 17:48:25 +0100 Subject: [PATCH 57/92] Ensure posted events from the ViewModel are consumed (once) by the UI Inspired from https://github.com/Kotlin/kotlinx.coroutines/issues/3002 --- .../app/core/platform/VectorBaseActivity.kt | 4 +- .../VectorBaseBottomSheetDialogFragment.kt | 4 +- .../app/core/platform/VectorBaseFragment.kt | 4 +- .../app/core/platform/VectorViewModel.kt | 9 +-- .../im/vector/app/core/utils/SharedEvent.kt | 58 +++++++++++++++++++ .../media/VectorAttachmentViewerActivity.kt | 3 +- .../settings/VectorSettingsBaseFragment.kt | 5 +- 7 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt 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 a87eb92b13..1e29dfff5e 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 @@ -128,9 +128,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver fun VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseActivity::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { hideWaitingView() observer(it) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index ad7a86c899..a44fb1c9ac 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -205,9 +205,11 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected fun VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { observer(it) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 9f79db9c66..a82cef54e5 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -277,9 +277,11 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseFragment::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { dismissLoadingDialog() observer(it) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index c9d58f9545..3dd38c455f 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -18,15 +18,16 @@ package im.vector.app.core.platform import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModel -import im.vector.app.core.utils.DataSource -import im.vector.app.core.utils.PublishDataSource +import im.vector.app.core.utils.EventQueue +import im.vector.app.core.utils.SharedEvents abstract class VectorViewModel(initialState: S) : MavericksViewModel(initialState) { // Used to post transient events to the View - protected val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents + protected val _viewEvents = EventQueue(capacity = 64) + val viewEvents: SharedEvents + get() = _viewEvents abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt new file mode 100644 index 0000000000..e712769c48 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt @@ -0,0 +1,58 @@ +/* + * 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.core.utils + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.transform +import java.util.concurrent.CopyOnWriteArraySet + +interface SharedEvents { + fun stream(consumerId: String): Flow +} + +class EventQueue(capacity: Int) : SharedEvents { + + private val innerQueue = MutableSharedFlow>(replay = capacity) + + fun post(event: T) { + innerQueue.tryEmit(OneTimeEvent(event)) + } + + override fun stream(consumerId: String): Flow = innerQueue.filterNotHandledBy(consumerId) +} + +/** + * Event designed to be delivered only once to a concrete entity, + * but it can also be delivered to multiple different entities. + * + * Keeps track of who has already handled its content. + */ +private class OneTimeEvent(private val content: T) { + + private val handlers = CopyOnWriteArraySet() + + /** + * @param asker Used to identify, whether this "asker" has already handled this Event. + * @return Event content or null if it has been already handled by asker + */ + fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null +} + +private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event -> + event.getIfNotHandled(consumerId)?.let { emit(it) } +} 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 9f9488e35d..0d240b376b 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 @@ -239,11 +239,12 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } private fun observeViewEvents() { + val tag = this::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel .viewEvents - .stream() + .stream(tag) .collect(::handleViewEvents) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 6299d8962d..724807a81e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -72,10 +72,11 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick protected fun VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorSettingsBaseFragment::class.simpleName.toString() lifecycleScope.launch { - repeatOnLifecycle(state) { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { observer(it) } From 9768430d5c19cc78a7d21c7dce375bb39890d26b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Dec 2022 18:32:07 +0100 Subject: [PATCH 58/92] Fix test compilation issue --- vector/src/test/java/im/vector/app/test/Extensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 2fbab3b71b..0b1a22f75c 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -28,7 +28,7 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "") fun VectorViewModel.test(): ViewModelTest { val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined) val state = stateFlow.test(testResultCollectingScope) - val viewEvents = viewEvents.stream().test(testResultCollectingScope) + val viewEvents = viewEvents.stream("test").test(testResultCollectingScope) return ViewModelTest(state, viewEvents) } From 7b1724f6dd1aa3651fc7a36890565cf9d213aa27 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Jan 2023 15:13:01 +0100 Subject: [PATCH 59/92] changelog --- changelog.d/7724.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7724.bugfix diff --git a/changelog.d/7724.bugfix b/changelog.d/7724.bugfix new file mode 100644 index 0000000000..685f7ad4e2 --- /dev/null +++ b/changelog.d/7724.bugfix @@ -0,0 +1 @@ + Observe ViewEvents only when resumed and ensure ViewEvents are not lost. From ea924642ce0e4eca0537f5f752e63c27d5fe5e3e Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Thu, 5 Jan 2023 16:51:43 +0000 Subject: [PATCH 60/92] Translated using Weblate (Danish) Currently translated at 10.2% (264 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/da/ --- .../src/main/res/values-da/strings.xml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/library/ui-strings/src/main/res/values-da/strings.xml b/library/ui-strings/src/main/res/values-da/strings.xml index 13d53b7bb2..35c93949f9 100644 --- a/library/ui-strings/src/main/res/values-da/strings.xml +++ b/library/ui-strings/src/main/res/values-da/strings.xml @@ -39,7 +39,6 @@ Telefonnummer Invitation til rum %1$s og %2$s - Tomt rum Lyst Tema Mørkt Tema @@ -82,7 +81,6 @@ Kun Matrix kontakter Ingen resultater Rum - Send logfiler Send crashlogfiler Send screenshot @@ -110,10 +108,8 @@ Dette ligner ikke en gyldig emailadresse Den emailadresse er allerede i brug. Glemt adgangskode? - Denne Home Server vil gerne være sikker på du ikke er en robot Kunne ikke verificere emailadresse: vær sikker på du klikkede på linket i emailen - Skriv gyldig URL Fejlformet JSON Indeholdt ikke gyldig JSON @@ -134,15 +130,10 @@ Opkald I Gang Den anden side tog den ikke. Information - - ${app_name} skal bruge tilladelse til at bruge din mikrofon for at lave lydopkald. - ${app_name} skal bruge tilladelse til at bruge dit kamera og din mikrofon for at lave videoopkald. Giv venligst tilladelse ved næste pop-up for at lave opkaldet. - - JA NEJ Fortsæt @@ -150,7 +141,6 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Forbind Afvis Spring til første ulæste besked. - Forlad rum Er du sikker på at du vil forlade rummet? DIREKTE CHATS @@ -163,7 +153,7 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet. Du vil ikke kunne omgøre denne ændring da du forfremmer brugeren til at have samme magt niveau som dig selv. Er du sikker? %s skriver… - "%1$s & %2$s skriver…" + %1$s & %2$s skriver… %1$s, %2$s og andre skriver… Du har ikke tilladelse til at skrive i dette rum Stol på @@ -186,7 +176,6 @@ Er du sikker? %d medlemsændringer Medlemsoversigt - 1 medlem %d medlemmer @@ -199,8 +188,6 @@ Er du sikker? Søg Filtrer medlemmer i rum Ingen resultater - - Alle meddelelser Opret genvej på startskærm Profilbillede @@ -291,4 +278,4 @@ Er du sikker? %1$s oprettede rummet Din invitation Forbind denne email med din konto - + \ No newline at end of file From 725722d3f29b42e033925491c8e6ca1d492aaf0e Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Thu, 5 Jan 2023 16:14:10 +0000 Subject: [PATCH 61/92] Translated using Weblate (Esperanto) Currently translated at 76.0% (1960 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ --- library/ui-strings/src/main/res/values-eo/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index f536ca00f9..4521e840a6 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -718,8 +718,8 @@ Bonvolu enigi la URL-on de identiga servilo Ne povis konektiĝi al identiga servilo Enigu URL-on de identiga servilo - Ni sendis al vi konfirman retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon - Ni sendis al vi konfirman retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon + Ni sendis retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon + Ni sendis retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon Troveblaj telefonnumeroj Malkonekto de via identiga servilo signifas, ke vi ne estos trovebla de aliaj uzantoj kaj ne povos inviti aliulojn per retpoŝtadreso aŭ telefono. Elektebloj pri trovado aperos post aldono de telefonnumero. @@ -2201,4 +2201,4 @@ Sonorante… Aroj - Iom uzantoj reatentita - + \ No newline at end of file From 3098ec140d384a7705d384444c30e7d8a44b81df Mon Sep 17 00:00:00 2001 From: overtinkering Date: Thu, 5 Jan 2023 18:02:33 +0000 Subject: [PATCH 62/92] Translated using Weblate (Spanish) Currently translated at 90.7% (2338 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ --- .../src/main/res/values-es/strings.xml | 109 ++++++++++-------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index e07c21d6a5..c06442b5d0 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -50,7 +50,7 @@ %1$s ha invitado a %2$s. Razón: %3$s %1$s te ha invitado. Razón: %2$s %1$s se ha unido. Razón: %2$s - %1$s se ha ido. Razón: %2$s + %1$s dejó la sala. Razón: %2$s %1$s ha rechadazo la invitación. Razón: %2$s %1$s expulsó a %2$s. Razón: %3$s %1$s ha baneado a %2$s. Razón: %3$s @@ -81,17 +81,17 @@ %1$s ha permitido que los invitados se unan a la sala. %1$s ha impedido que los invitados se unan a la sala. %1$s ha activado el cifrado Extremo-a-Extremo. - %1$s ha activado el cifrado Extremo-a-Extremo (algoritmo no reconocido %2$s). + %1$s ha activado el cifrado extremo-a-extremo (algoritmo no reconocido %2$s). Tu invitación %1$s creó la sala Creaste la sala Invitaste a %1$s Te uniste a la Sala - Dejaste la Sala + Dejaste la sala Rechazaste la invitación Tu pateaste a %1$s Tu desbanaste a %1$s - Usted prohibió a %1$s + Excluiste a %1$s Retiró la invitación de %1$s\'s Cambiaste tu avatar Establece su nombre de visualización en %1$s @@ -152,10 +152,10 @@ Agregaste %1$s y quitaste %2$s como direcciones para esta sala. Estableciste la dirección principal de esta sala en %1$s. Quitaste la dirección principal de esta sala. - Ha permitido que los invitados se unan a la sala. - Ha impedido que los invitados se unan a la sala. + Has permitido que los invitados se unan a la sala. + Has impedido que los invitados se unan a la sala. Has activado el cifrado Extremo-a-Extremo. - Has activado el cifrado Extremo-a-Extremo (algoritmo %1$s no reconocido). + Has activado el cifrado extremo-a-extremo (algoritmo %1$s no reconocido). Has impedido que invitados se unan a la sala. Has permitido a invitados unirse aquí. Te has ido. Razón: %1$s @@ -163,7 +163,7 @@ Has invitado a %1$s Has actualizado aquí. Has hecho futuros mensajes visibles a %1$s - Te saliste de la sala + Has dejado la sala Te uniste Creaste la conversación %1$s ha impedido que invitados se unan a la sala. @@ -255,7 +255,7 @@ Salas y Grupos Filtrar salas Invitaciones - Prioridad baja + Baja prioridad Conversaciones Solo contactos de Matrix No hay resultados @@ -429,7 +429,7 @@ Importar Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión. - SIN Verificar + Sin Verificar Verificado Verificar Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación: @@ -819,7 +819,7 @@ La copia de seguridad tiene una firma valida de la sesión no verificada %s La copia de seguridad tiene una firma inválida de la sesión verificada %s La copia de seguridad tiene una firma inválida de la sesión no verificada %s - Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora. + Para usar la copia de seguridad de la clave en esta sesión introduce tu contraseña o tu clave de recuperación ahora. ¿Deseas borrar tus claves de cifrado guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados. Reproducir sonido de cámara ip desconocida @@ -1182,7 +1182,7 @@ %s cancelada Cancelado por usted %s aceptada - Aceptado por usted + Aceptaste Verificacion enviada Solicitud de verificación Verifica esta Sesion @@ -1239,7 +1239,7 @@ Precaucion Error al obtener sesiones Sesiones - Confirmado + Confiable No es confiable Inicializar Firmas Cruzadas Restablecer claves @@ -1255,7 +1255,7 @@ Razón para redactar ${app_name} Android Refrescar - Nuevo inicio de sesión detectado . ¿Fue usted\? + Nuevo inicio de sesión detectado . ¿Has sido tú\? Este no era yo Su cuenta puede estar comprometida Verificación cancelada @@ -1263,7 +1263,7 @@ Clave de mensaje ¡Listo! Cifrado habilitado - Sala creada y configurada por usted. + Creaste y configuraste la sala. Esperando por %s… Ajuste de Notificaciones Mensaje… @@ -1279,7 +1279,7 @@ Si decea resetear su PIN, toque Olvidé PIN para cerrar sesión y restablecer. Numeros telefonicos Correos y numeros telefonicos - Administre el correo y numero telefonico de su cuenta + Administra las direcciones de correo y/o números telefónicos relacionados a tu cuenta de Matrix Mostrar mensajes eliminados Indicar marca de mensaje eliminado ARCHIVOS @@ -1357,7 +1357,7 @@ Hiciste la sala solo por invitación. Únase gratis a millones de personas en el mayor servidor público Continuar con SSO - Dirección de servicios de Element Matrix + Dirección de Element Matrix Services Ingrese la dirección del servidor que desea utilizar Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña. Siguiente @@ -1407,7 +1407,7 @@ Advertencia Tu cuenta aún no está creada. ¿Detener el proceso de registro\? Seleccione matrix.org - Seleccionar servicios de matriz de elementos + Seleccionar Element Matrix Services Seleccione un servidor doméstico personalizado Realiza el desafío de captcha Acepta los términos para continuar @@ -1453,7 +1453,7 @@ \nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta. Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado. La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no está suportado por ${app_name}. -\nPrimero borre los datos, luego inicie sesión nuevamente con otra cuenta. +\nPrimero borra los datos, luego inicia sesión nuevamente con otra cuenta. Su enlace matrix.to estaba mal formado El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores! Uno de los siguientes puede verse comprometido: @@ -1462,9 +1462,9 @@ \n- El servidor privado al que está conectado el usuario que estás verificando \n- Su conexión a internet o la de otros usuarios \n- Su dispositivo o el de otros usuarios - Los mensajes de esta sala están cifrados Extremo-a-Extremo. + Los mensajes de esta sala están cifrados de extremo-a-extremo. \n -\nSus mensajes están protegidos y sólo usted y el destinatario tienen las claves únicas para descifrarlos. +\nTus mensajes están protegidos y sólo tu y el destinatario tienen las claves únicas para descifrarlos. Esta sesión no puede compartir esta verificación con sus otras sesiones. \nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación. Envía el emote dado coloreado como un arcoíris @@ -1474,7 +1474,7 @@ Verifica si los mismos emojis aparecen en el mismo orden en ambos usuarios. Compare el código con el que se muestra en la pantalla del otro usuario. Los mensajes con este usuario están cifrados Extremo-a-Extremo y no pueden ser leídos por terceros. - Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza. + Tu nueva sesión acaba de verificarse y ahora tiene acceso a tus mensajes cifrados y otros usuarios la verán como de confianza. La firma cruzada está habilitada \n Claves privadas en el dispositivo. La firma cruzada está habilitada @@ -1484,8 +1484,8 @@ \nLas claves no son de confianza El administrador de su servidor ha desactivado el cifrado Extremo-a-Extremo de forma predeterminada en salas privadas y mensajes directos. No hay información criptográfica disponible - Esta sesión es confiable para mensajería segura porque usted la verificó: - Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida: + Esta sesión es confiable para mensajería segura porque la verificaste: + Verifica esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no iniciaste sesión en esta sesión, su cuenta puede haber sido comprometida: %d sesión activa %d sesiones activas @@ -1510,8 +1510,8 @@ Solicitudes clave Desbloquear el historial de mensajes cifrados Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes cifrados. - Si cancela, no podrá leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en él - Si cancela, no podrá leer mensajes cifrados en su nuevo dispositivo y otros usuarios no confiarán en él + Si cancelas, no podrás leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en este + Si cancelas, no podrás leer mensajes cifrados en tu nuevo dispositivo y otros usuarios no confiarán en este No verificarás %1$s (%2$s) si cancelas ahora. Comience de nuevo en su perfil de usuario. Uno de los siguientes puede verse comprometido: \n @@ -1524,7 +1524,7 @@ Se canceló la verificación. Puede iniciar la verificación de nuevo. Ingrese su %s para continuar. No use la contraseña de su cuenta. - Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. + Ingresa una frase de seguridad que solo tú conozcas, que se usa para proteger secretos en tu servidor. Esto puede tardar varios segundos, tenga paciencia. Configurando la recuperación. Manténlo seguro @@ -1561,7 +1561,7 @@ Nombre de usuario y / o contraseña incorrectos. La contraseña ingresada comienza o termina con espacios, verifíquela. Esta cuenta ha sido desactivada. Mejora de cifrado disponible - Verifíquese a usted mismo y a los demás para mantener sus chats seguros + Verifícate a ti mismo y a los demás para mantener tus chats seguros No es una clave de recuperación válida Por favor introduce una clave de recuperación Comprobando la clave de respaldo @@ -1622,11 +1622,11 @@ Usa una llave de seguridad Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Utilice una frase de seguridad - Ingrese una frase secreta que solo usted conozca y genere una clave de respaldo. + Ingresa una frase secreta que solo tú conozcas y genera una clave para tu copia de respaldo. Guarde su llave de seguridad Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Establecer una frase de seguridad - Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. + Ingresa una frase de seguridad que sólo tú conozcas, que se usa para proteger secretos en tu servidor. Frase de seguridad Ingrese su Frase de seguridad nuevamente para confirmarla. Nombre de la Sala @@ -1636,7 +1636,7 @@ Esperando este mensaje, esto puede tardar un poco Debido al cifrado Extremo-a-Extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de cifrado no se le enviaron correctamente. No puede acceder a este mensaje porque ha sido bloqueado por el remitente - No puede acceder a este mensaje porque el remitente no confía en su sesión + No puedes acceder a este mensaje porque el remitente no confía en tu sesión No puede acceder a este mensaje porque el remitente no envió las claves a propósito Esperando al historial de cifrado ¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta. @@ -1711,7 +1711,7 @@ Mostrar el dispositivo con el que puede verificar ahora Mostrar %d dispositivos con los que puede verificar ahora - Reiniciará sin historia, mensajes, dispositivos o usuarios verificados + Reiniciarás sin historial, ni mensajes, ni dispositivos o usuarios verificados Si resetea todo Solo haga esto si no tiene otro dispositivo con el que verificar éste. Resetear todo @@ -1748,7 +1748,7 @@ Se necesita una nueva autenticación ¡Código QR no escaneado! Código QR no válido (URL no válida)! - No puede DM usted mismo! + No puedes MD a ti mismo! Compartir por texto Cambiar PIN Cambie su PIN actual @@ -1960,7 +1960,7 @@ Acceso a la sala Siempre preguntar Espacios - mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. + Mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito. Mostrar salas con contenido explícito Directorio de la sala Salas sugeridas @@ -2181,7 +2181,7 @@ Agregar nuevas palabra clave Tus palabras clave Habilitar notificación por correo electrónico para %s - Para recibir un correo electrónico con una notificación, asocie un correo electrónico a su cuenta de matrix + Para recibir notificaciones por correo electrónico, asocia una direccion de correo electrónico a tu cuenta de Matrix Notificación de correo electrónico Ninguno Solo menciones y palabras clave @@ -2219,7 +2219,7 @@ No se puede grabar un mensaje de voz No se puede reproducir este mensaje de voz Toca tu grabación para detenerla o escucharla - %1$ds dejado + Restan %1$ds Mantenga presionado para grabar, suelte para enviar Eliminar grabación Grabación de mensaje de voz @@ -2238,7 +2238,7 @@ ¡Se ha cerrado la sesión! ¡Se ha abandonado la sala! Consejo: Pulse prolongadamente un mensaje y use \"%s\" . - Mantén las conversaciones organizadas con hilos + Mantén las conversaciones organizadas usando hilos Muestra todos los hilos en que has participado Mis Hilos Muestra todos los hilos de la sala actual @@ -2457,7 +2457,7 @@ BETA Comentarios de la beta de hilos Beta de hilos - - Algunos usuarios han sido dejados de ignorar + - Algunos usuarios han dejado de ser ignorados La compartición de pantalla está en progreso ${app_name} Compartición de pantalla Dejar de compartir pantalla @@ -2474,7 +2474,7 @@ Actualizado hace %1$s Implementación temporal: las ubicaciones persisten en el historial de la sala Activar compartir ubicación en tiempo real - Queda %1$s + Restan %1$s Compartiendo hasta %1$s Ver ubicación en tiempo real La ubicación en tiempo real ha terminado @@ -2625,8 +2625,8 @@ \nPor favor, inténtelo de nuevo.%s Usar ajustes por defecto del sistema Escoger manualmente - Tamaño automático de fuente - Escoger tamaño de la fuente + Tamaño automático + Escoge tamaño del tipo de letra %1$s y %2$d otro %1$s y %2$d otros @@ -2659,18 +2659,33 @@ Otorgar permiso ${app_name} necesita permiso para mostrar notificaciones. \nPor favor, otórgalo. - ${app_name} necesita permisos para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. + ${app_name} necesita permiso para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc. \n -\nPor favor, permite el acceso en las siguientes ventanas emergentes para poder visualizar notificaciones. - Prueba el editor de texto enriquecido (pronto llegará la opción de texto sin formato plain text) +\nPor favor, a continuacion, en las ventanas emergentes, permite el acceso para poder visualizar notificaciones. + Prueba el editor de texto enriquecido (pronto llegará la opción de texto simple, sin formato) Habilitar editor de texto enriquecido (rich text) Crear MD únicamente al primer mensaje Una versión simplificada de Element con pestañas opcionales Habilitar nueva disposición - Sí, Parar + Sí, Detener Deseleccionar todo - Ocultar los hijos de %s - Mostrar los hijos de %s + Ocultar los subespacios de %s + Mostrar los subespacios de %s Has finalizado una transmisión de voz. %1$s ha finalizado una transmisión de voz. + Element Matrix Services (EMS) es un servicio de alojamiento para tus comunicaciones en tiempo real. Robusto, confiable, rápido y seguro. Para saber cómo, ve a <a href=\"${ftue_ems_url}\">element.io/ems</a> + Difusión de voz + Habilitado: + ID de sesión: + Algo falló. Por favor, comprueba tu conexión de red e inténtalo nuevamente. + Citando + Respondiendo a %s + Editando + Abrir pantalla de herramientas de desarrollador + 🔒 Tienes habilitado el cifrado a sesiones verificadas sólo para todas las salas en Ajustes de Seguridad. + ⚠ Hay dispositivos sin verificar en esta sala, los cuales no seran capaces de descifrar los mensajes que envías. + Habilita MDs pospuestos + Mostrar chats recientes en el menú de compartir sistema + No enviar nunca mensajes cifrados a sesiones sin verificar en esta sala. + Restan %1$s \ No newline at end of file From f790921785b91b1d46fa5a48199047abf9420ac5 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Costa Date: Wed, 4 Jan 2023 21:12:32 +0000 Subject: [PATCH 63/92] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2576 of 2576 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 | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 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 bc617c62f0..a5aa778156 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 @@ -5,24 +5,24 @@ %1$s convidou você %1$s entrou na sala %1$s saiu da sala - %1$s rejeitou o convite + %1$s recusou o convite %1$s removeu %2$s %1$s desbaniu %2$s %1$s baniu %2$s - %1$s retirou o convite de %2$s - %1$s mudou o avatar dela(e) - %1$s definiu o nome de exibição dela(e) para %2$s - %1$s mudou o nome de exibição dela(e) de %2$s para %3$s - %1$s removeu o nome de exibição dela(e) (era %2$s) + %1$s desfez o convite para %2$s + %1$s mudou seu avatar + %1$s definiu seu nome de exibição para %2$s + %1$s mudou seu nome de exibição de %2$s para %3$s + %1$s removeu seu nome de exibição (era %2$s) %1$s mudou o tópico para: %2$s %1$s mudou o nome da sala para: %2$s %s começou uma chamada de vídeo. %s começou uma chamada de voz. %s atendeu a chamada. - %s terminou a chamada. - %1$s fez histórico futuro da sala visível para %2$s - todos os membros da sala, do ponto que foram convidados. - todos os membros da sala, do ponto que se juntaram. + %s encerrou a chamada. + %1$s tornou o histórico futuro da sala visível para %2$s + todos os membros da sala, a partir do ponto que foram convidados. + todos os membros da sala, a partir do ponto que entraram. todos os membros da sala. qualquer pessoa. (avatar mudou também) @@ -45,11 +45,11 @@ Você convidou %1$s Você entrou na sala Você saiu da sala - Você rejeitou o convite + Você recusou o convite Você removeu %1$s Você desbaniu %1$s Você baniu %1$s - Você retirou o convite de %1$s + Você desfez o convite para %1$s Você mudou seu avatar Você definiu seu nome de exibição para %1$s Você mudou seu nome de exibição de %1$s para %2$s @@ -63,8 +63,8 @@ %s enviou dados para configurar a chamada. Você enviou dados para configurar a chamada. Você atendeu a chamada. - Você terminou a chamada. - Você fez histórico futuro da sala visível para %1$s + Você encerrou a chamada. + Você tornou o histórico futuro da sala visível para %1$s %s fez o upgrade desta sala. Você fez o upgrade desta sala. Você removeu o nome da sala @@ -170,8 +170,8 @@ %1$s convidou %2$s Você fez o upgrade aqui. %s fez o upgrade aqui. - Você fez mensagens futuras visíveis para %1$s - %1$s fez mensagens futuras visíveis para %2$s + Você tornou as mensagens futuras visíveis para %1$s + %1$s tornou as mensagens futuras visíveis para %2$s Você saiu da sala %1$s saiu da sala Você entrou @@ -408,7 +408,7 @@ Qualquer pessoa Membros somente (desde o ponto no tempo de seleção desta opção) Membros somente (desde que eles foram convidados) - Membros somente (desde que eles se juntaram) + Membros somente (desde que eles entraram) Usuárias(os) banidas(os) Avançadas ID interno desta sala @@ -2009,8 +2009,8 @@ Adicionar salas Explorar salas - %d pessoa que você conhece já tem se juntado - %d pessoas que você conhece já têm se juntado + %d pessoa que você conhece já entrou + %d pessoas que você conhece já entraram Juntar-Se a Espaço Criar espaço @@ -2833,8 +2833,8 @@ Desselecionar todas(os) Selecionar todas(os) - %1$d selecionada(o) - %1$d selecionadas(os) + %1$d selecionado(a) + %1$d selecionados(as) Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo. Alternar modo de tela cheia From ed84212c78d176c06760c9be65a097d11781e7ed Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Wed, 4 Jan 2023 17:01:03 +0000 Subject: [PATCH 64/92] Translated using Weblate (Albanian) Currently translated at 99.3% (2558 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- library/ui-strings/src/main/res/values-sq/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index 3b233c087c..b170d306e4 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2504,7 +2504,7 @@ %s \nduket paksa si i zbrazët. Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës. - Aktivizoni transmetim zanor + Aktivizoni transmetim zanor (nën zhvillim aktiv) Aktivizo regjistrim hollësish klienti Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj. Aktivizo përgjegjës të ri sesionesh From 860df019025ac652ef265814afa8eec759ce099d Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 4 Jan 2023 16:20:15 +0000 Subject: [PATCH 65/92] Translated using Weblate (German) Currently translated at 100.0% (89 of 89 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/40105160.txt | 2 ++ fastlane/metadata/android/de-DE/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105180.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40105160.txt b/fastlane/metadata/android/de-DE/changelogs/40105160.txt new file mode 100644 index 0000000000..c55d8d998f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105180.txt b/fastlane/metadata/android/de-DE/changelogs/40105180.txt new file mode 100644 index 0000000000..c55d8d998f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases From bd21f032d4a53d0292f4c580c237cf573ba74361 Mon Sep 17 00:00:00 2001 From: Glandos Date: Thu, 5 Jan 2023 08:42:07 +0000 Subject: [PATCH 66/92] Translated using Weblate (French) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr-FR/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/fr-FR/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105180.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105160.txt b/fastlane/metadata/android/fr-FR/changelogs/40105160.txt new file mode 100644 index 0000000000..4101bb0c86 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Fils de discussion activés par défaut. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105180.txt b/fastlane/metadata/android/fr-FR/changelogs/40105180.txt new file mode 100644 index 0000000000..4101bb0c86 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Fils de discussion activés par défaut. +Intégralité des changements : https://github.com/vector-im/element-android/releases From 271b828be09c638cbc43a30708be8404493ac255 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 4 Jan 2023 18:55:35 +0000 Subject: [PATCH 67/92] Translated using Weblate (Hungarian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ --- fastlane/metadata/android/hu-HU/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/hu-HU/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105180.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105160.txt b/fastlane/metadata/android/hu-HU/changelogs/40105160.txt new file mode 100644 index 0000000000..c5dc38bc8f --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Legnagyobb változtatás ebben a verzióban: Új üzenetszálak alapból bekapcsolva! +Teljes változási napló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105180.txt b/fastlane/metadata/android/hu-HU/changelogs/40105180.txt new file mode 100644 index 0000000000..cc70967e58 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Legnagyobb változtatás ebben a verzióban: Az üzenetszálak alapból bekapcsolva! +Teljes változási napló: https://github.com/vector-im/element-android/releases From 96363fb789f82acbb24d1bb55f1c3bad505431c5 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 4 Jan 2023 16:18:18 +0000 Subject: [PATCH 68/92] Translated using Weblate (Slovak) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/sk/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/sk/changelogs/40105180.txt diff --git a/fastlane/metadata/android/sk/changelogs/40105160.txt b/fastlane/metadata/android/sk/changelogs/40105160.txt new file mode 100644 index 0000000000..d5b5ad330d --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105180.txt b/fastlane/metadata/android/sk/changelogs/40105180.txt new file mode 100644 index 0000000000..d5b5ad330d --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases From 2903a644f2e258f6509e7dfa126bca5bcfed9c37 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 4 Jan 2023 19:24:53 +0000 Subject: [PATCH 69/92] Translated using Weblate (Ukrainian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/uk/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/uk/changelogs/40105180.txt diff --git a/fastlane/metadata/android/uk/changelogs/40105160.txt b/fastlane/metadata/android/uk/changelogs/40105160.txt new file mode 100644 index 0000000000..edbd209d17 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Гілки відтепер типово ввімкнено. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105180.txt b/fastlane/metadata/android/uk/changelogs/40105180.txt new file mode 100644 index 0000000000..edbd209d17 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Гілки відтепер типово ввімкнено. +Перелік усіх змін: https://github.com/vector-im/element-android/releases From 8a5aad1ba0a3ee66e40b10f05fafa22641cf63dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 5 Jan 2023 07:22:25 +0000 Subject: [PATCH 70/92] Translated using Weblate (Estonian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/et/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/et/changelogs/40105180.txt diff --git a/fastlane/metadata/android/et/changelogs/40105160.txt b/fastlane/metadata/android/et/changelogs/40105160.txt new file mode 100644 index 0000000000..9aadf5dae8 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105180.txt b/fastlane/metadata/android/et/changelogs/40105180.txt new file mode 100644 index 0000000000..9aadf5dae8 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases From ff9cf8fd2f0e3edcfb0d55bddf86dad31938cdb3 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 4 Jan 2023 17:43:34 +0000 Subject: [PATCH 71/92] Translated using Weblate (Persian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40105180.txt diff --git a/fastlane/metadata/android/fa/changelogs/40105160.txt b/fastlane/metadata/android/fa/changelogs/40105160.txt new file mode 100644 index 0000000000..0c3cc5aa31 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105160.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105180.txt b/fastlane/metadata/android/fa/changelogs/40105180.txt new file mode 100644 index 0000000000..0c3cc5aa31 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105180.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رشته‌ها اکنون به صورت پیش‌گزیده به کار افتاده‌اند. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases From 53db9885252338dd8aecc7ce88f1832ff00401fd Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Thu, 5 Jan 2023 16:06:48 +0000 Subject: [PATCH 72/92] Translated using Weblate (Esperanto) Currently translated at 2.2% (2 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/eo/ --- fastlane/metadata/android/eo/short_description.txt | 2 +- fastlane/metadata/android/eo/title.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/metadata/android/eo/short_description.txt b/fastlane/metadata/android/eo/short_description.txt index 33013ce78f..05a4aaf191 100644 --- a/fastlane/metadata/android/eo/short_description.txt +++ b/fastlane/metadata/android/eo/short_description.txt @@ -1 +1 @@ -Sekura kaj sencentrigita vokado kaj babilado. Tenu viajn datumojn sekuraj. +Grupa mesaĝisto - ĉifrita mesaĝado, grupa babilejo kaj videovokoj diff --git a/fastlane/metadata/android/eo/title.txt b/fastlane/metadata/android/eo/title.txt index f56927e529..85b92c693b 100644 --- a/fastlane/metadata/android/eo/title.txt +++ b/fastlane/metadata/android/eo/title.txt @@ -1 +1 @@ -Element (antaŭe Riot.im) +Element - Sekura Tujmesaĝilo From 0882e1bf81b17f4afb84cb2dcf11c4bae38d44c9 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 5 Jan 2023 02:10:13 +0000 Subject: [PATCH 73/92] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (89 of 89 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/40105160.txt | 2 ++ fastlane/metadata/android/zh-TW/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105180.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105160.txt b/fastlane/metadata/android/zh-TW/changelogs/40105160.txt new file mode 100644 index 0000000000..9c66f3c2ad --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105160.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串現在預設啟用。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105180.txt b/fastlane/metadata/android/zh-TW/changelogs/40105180.txt new file mode 100644 index 0000000000..9c66f3c2ad --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105180.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串現在預設啟用。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases From 5734a270d8b1630f43543747174336191fe2bdb5 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Wed, 4 Jan 2023 16:04:26 +0000 Subject: [PATCH 74/92] Translated using Weblate (Czech) Currently translated at 100.0% (89 of 89 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/40105160.txt | 2 ++ fastlane/metadata/android/cs-CZ/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105180.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt new file mode 100644 index 0000000000..69c2b3304c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt new file mode 100644 index 0000000000..69c2b3304c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení. +Úplný seznam změn: https://github.com/vector-im/element-android/releases From 4f2550ae923b1be83c41c501a2283f37d3bfe4f7 Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 4 Jan 2023 22:47:24 +0000 Subject: [PATCH 75/92] Translated using Weblate (Indonesian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/id/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/id/changelogs/40105180.txt diff --git a/fastlane/metadata/android/id/changelogs/40105160.txt b/fastlane/metadata/android/id/changelogs/40105160.txt new file mode 100644 index 0000000000..173a1bfb1b --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105180.txt b/fastlane/metadata/android/id/changelogs/40105180.txt new file mode 100644 index 0000000000..173a1bfb1b --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases From e9d1de8fbac8d93f6a98aa3cc706f6ad16ea8f10 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Jan 2023 17:36:40 +0100 Subject: [PATCH 76/92] Fix compilation issue after rebase. --- .../core/platform/VectorBaseDialogFragment.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt index 5a817b989e..34e233aa7a 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt @@ -23,8 +23,10 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import dagger.hilt.android.EntryPointAccessors @@ -37,6 +39,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -145,11 +148,15 @@ abstract class VectorBaseDialogFragment : DialogFragment(), Ma * ========================================================================================== */ protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { - viewEvents - .stream() - .onEach { - observer(it) - } - .launchIn(viewLifecycleOwner.lifecycleScope) + val tag = this@VectorBaseDialogFragment::class.simpleName.toString() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewEvents + .stream(tag) + .collect { + observer(it) + } + } + } } } From 8784b7d3b7484fe346acf509bd0be148b7b3ced3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:18:15 +0000 Subject: [PATCH 77/92] Bump wysiwyg from 0.13.0 to 0.14.0 (#7902) Bumps [wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.13.0...0.14.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg 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> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 76ff9b0c6f..e970457e7c 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.13.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.14.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From d0880095eab859a60dcfb9d33b9a4942d8fe009b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:03:28 +0000 Subject: [PATCH 78/92] Bump dependency-check-gradle from 7.4.3 to 7.4.4 Bumps dependency-check-gradle from 7.4.3 to 7.4.4. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle 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 7e5d659c8b..cdbfcc44b7 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' 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.4.3' + classpath 'org.owasp:dependency-check-gradle:7.4.4' 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 c2c8df9e8eb95c360f27dde0cdffbb7898394804 Mon Sep 17 00:00:00 2001 From: Vri Date: Fri, 6 Jan 2023 17:22:57 +0000 Subject: [PATCH 79/92] Translated using Weblate (German) Currently translated at 100.0% (2585 of 2585 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 | 9 +++++++++ 1 file changed, 9 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 3f2ea694ca..52b8f0c716 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2889,4 +2889,13 @@ Link erstellen Link Text + Dein Zugriffstoken gewährt vollen Zugriff auf dein Konto. Teile ihn mit niemandem. + Zugriffstoken + Unsortierte Liste umschalten + Nummerierte Liste umschalten + In diesem Raum gibt es noch keine abgeschlossenen Umfragen + Vergangene Umfragen + In diesem Raum gibt es keine aktiven Umfragen + Aktive Umfragen + Umfrageverlauf \ No newline at end of file From c0010140a3e8909132ebad7252d5e7532a2dad04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 7 Jan 2023 03:09:19 +0000 Subject: [PATCH 80/92] Translated using Weblate (Estonian) Currently translated at 99.6% (2577 of 2585 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 | 9 +++++++++ 1 file changed, 9 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 490145b5cc..1e8e2b989e 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2881,4 +2881,13 @@ Link Loo link Muuda linki + Küsitluste ajalugu + Käimasolevad küsitlused + Selles jututoas pole käimasolevaid küsitlusi + Varasemad küsitlused + Selles jututoas pole varasemaid küsitlusi + Lülita nummerdatud loend sisse/välja + Lülita täpploend sisse/välja + Pääsuluba + Sinu pääsuluba annab täismahulise ligipääsu sinu kasutajakontole. Palun ära jaga seda teistega. \ No newline at end of file From 8c3fcf989ea8d483b46af7e91ffd6d7c2d947f39 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 7 Jan 2023 00:08:56 +0000 Subject: [PATCH 81/92] Translated using Weblate (Persian) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- library/ui-strings/src/main/res/values-fa/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) 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 56dbe912c1..4db3812237 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2890,4 +2890,13 @@ پیوند متن تنظیم پیوند + ژتون دسترسیتان، دسترسی کامل به حسابتان را می‌دهد. با هیچ‌کس هم‌رسانیش نکنید. + ژتون دسترسی + تغییر وضعیت سیاههٔ گلوله‌ای + تغییر وضعیت سیاههٔ شماره‌دار + هیچ‌ نظرسنجی قدیمی‌ای در این اتاق وجود ندارد + نظرسنجی‌های گذشته + هیچ نظرسنجی فعّالی در این اتاق وجود ندارد + نظرسنجی‌های فعّال + تاریخچهٔ نظرسنجی‌ها \ No newline at end of file From 6840e52d30b2dc23de26faa9b142ac9efe0ff541 Mon Sep 17 00:00:00 2001 From: Glandos Date: Sat, 7 Jan 2023 13:34:15 +0000 Subject: [PATCH 82/92] Translated using Weblate (French) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- library/ui-strings/src/main/res/values-fr/strings.xml | 9 +++++++++ 1 file changed, 9 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 33f1410502..cb1684f834 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2890,4 +2890,13 @@ Lien Texte Définir un lien + Votre jeton d’accès donne un accès intégral à votre compte. Ne le partagez avec personne. + Jeton d’accès + (Dés)activer la liste à puce + (Dés)activer la liste numérotée + Il n’y a aucun ancien sondage dans ce salon + Anciens sondages + Il n’y a aucun sondage en cours dans ce salon + Sondages actifs + Historique des sondages \ No newline at end of file From 1deceaecdb068ed9c5a7b76e0b0b0d6dc0f51c22 Mon Sep 17 00:00:00 2001 From: Linerly Date: Fri, 6 Jan 2023 17:52:08 +0000 Subject: [PATCH 83/92] Translated using Weblate (Indonesian) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- library/ui-strings/src/main/res/values-in/strings.xml | 9 +++++++++ 1 file changed, 9 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 8715aa84e0..8896037037 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2836,4 +2836,13 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tautan Teks Atur tautan + Token akses Anda memberikan akses penuh ke akun Anda. Jangan bagikan dengan siapa pun. + Token Akses + Saklar daftar bulat + Saklar daftar bernomor + Tidak ada pemungutan suara sebelumnya di ruangan ini + Pemungutan suara sebelumnya + Tidak ada pemungutan suara yang aktif di ruangan ini + Pemungutan suara aktif + Riwayat pemungutan suara \ No newline at end of file From f5c489e5f70e692ac071b6b67ccb72f4b2463eff Mon Sep 17 00:00:00 2001 From: random Date: Fri, 6 Jan 2023 17:12:48 +0000 Subject: [PATCH 84/92] Translated using Weblate (Italian) Currently translated at 100.0% (2585 of 2585 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 | 9 +++++++++ 1 file changed, 9 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 b1721449ad..729b826982 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2881,4 +2881,13 @@ Collegamento Testo Imposta collegamento + Il tuo token di accesso ti dà l\'accesso al tuo account. Non condividerlo con nessuno. + Token di accesso + Attiva/disattiva elenco numerato + Attiva/disattiva elenco puntato + In questa stanza non ci sono sondaggi passati + Sondaggi passati + In questa stanza non ci sono sondaggi attivi + Sondaggi attivi + Cronologia sondaggi \ No newline at end of file From e7e8e485a05a5f61f70ec7d6e708cd16c42d16f5 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Fri, 6 Jan 2023 17:55:33 +0000 Subject: [PATCH 85/92] Translated using Weblate (Slovak) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- library/ui-strings/src/main/res/values-sk/strings.xml | 9 +++++++++ 1 file changed, 9 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 d46b4e8898..34155ba6a5 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2946,4 +2946,13 @@ Odkaz Text Nastaviť odkaz + Váš prístupový token poskytuje úplný prístup k vášmu účtu. S nikým ho nezdieľajte. + Prístupový token + Prepnúť na číslovaný zoznam + Prepnúť zoznam s odrážkami + V tejto miestnosti nie sú žiadne predchádzajúce ankety + Predchádzajúce ankety + V tejto miestnosti nie sú žiadne aktívne ankety + Aktívne ankety + História ankety \ No newline at end of file From dd148443b28eb65ad4671c2df1018128ef3a7e6a Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 6 Jan 2023 16:53:23 +0000 Subject: [PATCH 86/92] Translated using Weblate (Albanian) Currently translated at 99.2% (2566 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- library/ui-strings/src/main/res/values-sq/strings.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index b170d306e4..c3f9d53c99 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2504,7 +2504,7 @@ %s \nduket paksa si i zbrazët. Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës. - Aktivizoni transmetim zanor (nën zhvillim aktiv) + Aktivizoni transmetim zanor Aktivizo regjistrim hollësish klienti Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj. Aktivizo përgjegjës të ri sesionesh @@ -2874,4 +2874,12 @@ Krijoni një lidhje Lidhje Tekst + Tokeni juaj i hyrjeve jep hyrje të plotë në llogarinë tuaj. Mos ia jepni kujt. + Token Hyrjesh + S’ka pyetësorë të kaluar në këtë dhomë + Pyetësorë të kaluar + S’ka pyetësorë aktivë në këtë dhomë + Pyetësorë aktivë + mundësia fituese + Historik pyetësorësh \ No newline at end of file From 9ae418ddeaa0d0e1705073bfecf160cf9c98d956 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 6 Jan 2023 17:47:53 +0000 Subject: [PATCH 87/92] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2585 of 2585 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 | 9 +++++++++ 1 file changed, 9 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 ebe2dcaf4d..2ee9685c76 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3002,4 +3002,13 @@ Посилання Текст Налаштувати посилання + Ваш токен доступу надає повний доступ до вашого облікового запису. Не передавайте його нікому. + Токен доступу + Перемкнути на маркований список + Перемкнути на нумерований список + У цій кімнаті ще не проводилися опитування + Минулі опитування + У цій кімнаті немає активних опитувань + Активні опитування + Історія опитувань \ No newline at end of file From 1dec87356f1ff4c628527f1a1a28dc4240d4453c Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 7 Jan 2023 01:31:47 +0000 Subject: [PATCH 88/92] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2585 of 2585 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- .../ui-strings/src/main/res/values-zh-rTW/strings.xml | 9 +++++++++ 1 file changed, 9 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 934f5aa47b..14729c5b44 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 @@ -2834,4 +2834,13 @@ 連結 文字 設定連結 + 您的存取權杖可以完整存取您的帳號。不要將其與其他人分享。 + 存取權杖 + 切換項目符號清單 + 切換編號清單 + 此聊天室沒有過去的投票 + 過去的投票 + 此聊天室沒有正在進行的投票 + 進行中的投票 + 投票歷史紀錄 \ No newline at end of file From 096a8fdd3db894d16fc7297c129cbe0cb83c163b Mon Sep 17 00:00:00 2001 From: random Date: Fri, 6 Jan 2023 17:13:09 +0000 Subject: [PATCH 89/92] Translated using Weblate (Italian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/it-IT/changelogs/40105180.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/40105180.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40105160.txt b/fastlane/metadata/android/it-IT/changelogs/40105160.txt new file mode 100644 index 0000000000..ab24842e82 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105160.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: i messaggi in conversazioni sono attivi in modo predefinito. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105180.txt b/fastlane/metadata/android/it-IT/changelogs/40105180.txt new file mode 100644 index 0000000000..ab24842e82 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105180.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: i messaggi in conversazioni sono attivi in modo predefinito. +Cronologia completa: https://github.com/vector-im/element-android/releases From ce0c514daef9b1b68ab59c0599c5d3a9a799a823 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Jan 2023 11:55:42 +0100 Subject: [PATCH 90/92] Fix link on doc. --- .../android/sdk/api/session/room/threads/ThreadsService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt index bb6f6b51d3..dfa6cdeec1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary * This interface defines methods to interact with thread related features. * It's the dynamic threads implementation and the homeserver must return * a capability entry for threads. If the server do not support m.thread - * then [ThreadsLocalService] should be used instead + * then [org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService] should be used instead */ interface ThreadsService { From ca3d441de03015dd0039a97741c2c5bf95228bb0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Jan 2023 14:06:12 +0100 Subject: [PATCH 91/92] Handle error in the thread list request. --- .../src/main/res/values/strings.xml | 1 + .../android/sdk/api/failure/Extensions.kt | 3 ++ .../room/threads/FetchThreadsResult.kt | 1 - .../list/viewmodel/ThreadListViewActions.kt | 23 ++++++++++ .../list/viewmodel/ThreadListViewEvents.kt | 23 ++++++++++ .../list/viewmodel/ThreadListViewModel.kt | 44 ++++++++++++------- .../threads/list/views/ThreadListFragment.kt | 39 ++++++++++++++++ 7 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7c75f36a3b..d9f94ba27b 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -794,6 +794,7 @@ Shows all threads you’ve participated in Keep discussions organized with threads Threads help keep your conversations on-topic and easy to track. + You\'re homeserver does not support listing threads yet. Tip: Long tap a message and use “%s”. From a Thread diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 5b41ddaaec..165dcf079e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -25,6 +25,9 @@ import java.io.IOException import java.net.UnknownHostException import javax.net.ssl.HttpsURLConnection +fun Throwable.is400() = this is Failure.ServerError && + httpCode == HttpsURLConnection.HTTP_BAD_REQUEST + fun Throwable.is401() = this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED && /* 401 */ error.code == MatrixError.M_UNAUTHORIZED diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt index 5d4d67a65e..e3c5deeee7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt @@ -19,5 +19,4 @@ package org.matrix.android.sdk.api.session.room.threads sealed class FetchThreadsResult { data class ShouldFetchMore(val nextBatch: String) : FetchThreadsResult() object ReachedEnd : FetchThreadsResult() - object Failed : FetchThreadsResult() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt new file mode 100644 index 0000000000..7dda460a5e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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.threads.list.viewmodel + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface ThreadListViewActions : VectorViewModelAction { + object TryAgain : ThreadListViewActions +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt new file mode 100644 index 0000000000..3e9af034f4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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.threads.list.viewmodel + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface ThreadListViewEvents : VectorViewEvents { + data class ShowError(val throwable: Throwable) : ThreadListViewEvents +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 7124727bb7..f31f19849c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -27,8 +27,6 @@ import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import im.vector.app.core.platform.EmptyAction -import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsInteraction @@ -52,7 +50,7 @@ class ThreadListViewModel @AssistedInject constructor( @Assisted val initialState: ThreadListViewState, private val analyticsTracker: AnalyticsTracker, private val session: Session, -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @@ -93,7 +91,17 @@ class ThreadListViewModel @AssistedInject constructor( fetchAndObserveThreads() } - override fun handle(action: EmptyAction) {} + override fun handle(action: ThreadListViewActions) { + when (action) { + ThreadListViewActions.TryAgain -> handleTryAgain() + } + } + + private fun handleTryAgain() { + viewModelScope.launch { + fetchNextPage() + } + } /** * Observing thread list with respect to homeserver capabilities. @@ -181,21 +189,23 @@ class ThreadListViewModel @AssistedInject constructor( true -> ThreadFilter.PARTICIPATED false -> ThreadFilter.ALL } - room?.threadsService()?.fetchThreadList( - nextBatchId = nextBatchId, - limit = defaultPagedListConfig.pageSize, - filter = filter, - ).let { result -> - when (result) { - is FetchThreadsResult.ReachedEnd -> { - hasReachedEnd = true - } - is FetchThreadsResult.ShouldFetchMore -> { - nextBatchId = result.nextBatch - } - else -> { + try { + room?.threadsService()?.fetchThreadList( + nextBatchId = nextBatchId, + limit = defaultPagedListConfig.pageSize, + filter = filter, + )?.let { result -> + when (result) { + is FetchThreadsResult.ReachedEnd -> { + hasReachedEnd = true + } + is FetchThreadsResult.ShouldFetchMore -> { + nextBatchId = result.nextBatch + } } } + } catch (throwable: Throwable) { + _viewEvents.post(ThreadListViewEvents.ShowError(throwable)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index 318c250906..1e67941856 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -26,6 +26,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup @@ -41,10 +42,14 @@ import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListPagedController +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewActions +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewEvents import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.ReportType +import org.matrix.android.sdk.api.failure.is400 +import org.matrix.android.sdk.api.failure.is404 import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem @@ -126,11 +131,45 @@ class ThreadListFragment : views.threadListRecyclerView.configureWith(legacyThreadListController, TimelineItemAnimator(), hasFixedSize = false) legacyThreadListController.listener = this } + observeViewEvents() + } + + private fun observeViewEvents() { + threadListViewModel.observeViewEvents { + when (it) { + is ThreadListViewEvents.ShowError -> handleShowError(it) + } + } + } + + private fun handleShowError(event: ThreadListViewEvents.ShowError) { + val error = event.throwable + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .also { + if (error.is400() || error.is404()) { + // Outdated Homeserver + it.setMessage(R.string.thread_list_not_available) + it.setPositiveButton(R.string.ok) { _, _ -> + requireActivity().finish() + } + } else { + // Other error, can retry + // (Can happen on first request or on pagination request) + it.setMessage(errorFormatter.toHumanReadable(error)) + it.setPositiveButton(R.string.ok, null) + it.setNegativeButton(R.string.global_retry) { _, _ -> + threadListViewModel.handle(ThreadListViewActions.TryAgain) + } + } + } + .show() } override fun onDestroyView() { views.threadListRecyclerView.cleanup() threadListController.listener = null + legacyThreadListController.listener = null super.onDestroyView() } From 81234b3a5bc94a3a5ee9011f92f76044856cad71 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Jan 2023 14:37:35 +0100 Subject: [PATCH 92/92] Changelog --- changelog.d/7913.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7913.bugfix diff --git a/changelog.d/7913.bugfix b/changelog.d/7913.bugfix new file mode 100644 index 0000000000..32b821f14d --- /dev/null +++ b/changelog.d/7913.bugfix @@ -0,0 +1 @@ +Handle network error on API `rooms/{roomId}/threads`