From 9a651b223b6c6b9e6ccfe643c49e7ae084bf957e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Sep 2022 13:18:18 +0200 Subject: [PATCH 01/32] Use `buildjet-4vcpu-ubuntu-2204` runner instead of `macos-latest` to build and run the integration tests. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cd7e26f3cf..8a0d320bbf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ env: jobs: tests: name: Runs all tests - runs-on: macos-latest # for the emulator + runs-on: buildjet-4vcpu-ubuntu-2204 # for the emulator # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} From 2e8b6e4eb9a7576ce51058b593033b288f2053d8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Sep 2022 14:40:26 +0200 Subject: [PATCH 02/32] typo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a0d320bbf..148a026b84 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,7 +53,7 @@ jobs: ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES - # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure' + # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves steps.tests.outcome = 'failure' - name: Run all the codecoverage tests at once (retry if emulator failed) uses: reactivecircus/android-emulator-runner@v2 if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. From 6ac9a7627b451c84760b1ebf463f0ace34b693ff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Sep 2022 14:42:39 +0200 Subject: [PATCH 03/32] Disable 2nd attempt to run the tests. --- .github/workflows/tests.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 148a026b84..ffffb2b760 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,22 +54,22 @@ jobs: ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves steps.tests.outcome = 'failure' - - name: Run all the codecoverage tests at once (retry if emulator failed) - uses: reactivecircus/android-emulator-runner@v2 - if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. - with: - api-level: 28 - arch: x86 - profile: Nexus 5X - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - emulator-build: 7425822 - script: | - ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES - ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES - ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES - ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES + ### - name: Run all the codecoverage tests at once (retry if emulator failed) + ### uses: reactivecircus/android-emulator-runner@v2 + ### if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. + ### with: + ### api-level: 28 + ### arch: x86 + ### profile: Nexus 5X + ### force-avd-creation: false + ### emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + ### disable-animations: true + ### emulator-build: 7425822 + ### script: | + ### ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES + ### ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES + ### ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES + ### ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES # we may have failed a previous step and retried, that's OK - name: Publish results to Sonar From 1afe0981a68ba472ccad834791dde9281a6750d4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 13 Sep 2022 14:47:35 +0200 Subject: [PATCH 04/32] Use `buildjet-4vcpu-ubuntu-2204` runner instead of `macos-latest` to build and run the integration tests for the post merge task. --- .github/workflows/post-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index 5cde95e625..bf948064ed 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -31,7 +31,7 @@ jobs: ui-tests: name: UI Tests (Synapse) needs: should-i-run - runs-on: macos-latest + runs-on: buildjet-4vcpu-ubuntu-2204 strategy: fail-fast: false matrix: From 0685fb1e1a16cb2dce12ed2e2785db31e8efab46 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Sep 2022 19:03:19 +0200 Subject: [PATCH 05/32] Changelog --- changelog.d/7108.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7108.misc diff --git a/changelog.d/7108.misc b/changelog.d/7108.misc new file mode 100644 index 0000000000..d4b15a0222 --- /dev/null +++ b/changelog.d/7108.misc @@ -0,0 +1 @@ +Move some GitHub action to buildjet runner, and remove the second attempt to run integration tests. From b8b2601e0b2534c2c0e9b3e3de0bae06281e652e Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Sat, 17 Sep 2022 13:12:45 -0400 Subject: [PATCH 06/32] Enables app layout by default in labs --- vector-config/src/main/res/values/config-settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index 1701fd45b0..8953138e5e 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -38,7 +38,7 @@ false - false + true true false From 57c9161e00f19569cd94267fdd1339b01c8f8f20 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Sat, 17 Sep 2022 13:17:39 -0400 Subject: [PATCH 07/32] Adds changelog file --- changelog.d/7166.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7166.misc diff --git a/changelog.d/7166.misc b/changelog.d/7166.misc new file mode 100644 index 0000000000..d223208853 --- /dev/null +++ b/changelog.d/7166.misc @@ -0,0 +1 @@ +New App Layout is now enabled by default! Go to the Settings > Labs to toggle this From 298aaece01acab6c44afedf4988839abfcbcb170 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Sun, 18 Sep 2022 18:02:45 +0200 Subject: [PATCH 08/32] fixed checkVerifyPopup test fail --- .../java/im/vector/app/VerifySessionInteractiveTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index da13e49e84..901ef8e4c1 100644 --- a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -225,8 +225,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() { // Wait until local secrets are known (gossip) withIdlingResource(allSecretsKnownIdling(uiSession)) { - onView(withId(R.id.groupToolbarAvatarImageView)) - .perform(click()) + onView(withId(R.id.roomListContainer)) + .check(matches(isDisplayed())) } } From e76793781dd4ff969cff34c4c57846aae3e9536b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 12 Sep 2022 17:51:38 +0200 Subject: [PATCH 09/32] Use LocalRoomSummaryEntity.where extension --- .../session/room/create/CreateRoomFromLocalRoomTask.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 02538a5cc3..d73ffbfbe3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy -import io.realm.kotlin.where import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue @@ -36,11 +35,11 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId @@ -86,8 +85,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( var createRoomParams: CreateRoomParams? = null var isEncrypted = false monarchy.doWithRealm { realm -> - realm.where() - .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId) + LocalRoomSummaryEntity.where(realm, params.localRoomId) .findFirst() ?.let { createRoomParams = it.createRoomParams From 824a4bcae5f47cb83ac3b1a1aeb16b2eebd1e120 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 12 Sep 2022 17:52:27 +0200 Subject: [PATCH 10/32] Add comment to explain the replacementRoom behaviour --- .../internal/session/room/create/CreateRoomFromLocalRoomTask.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index d73ffbfbe3..246b6aa241 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -74,6 +74,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( get() = monarchy.realmConfiguration override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { + // If a room has already been created for the given local room, return the existing roomId val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) ?.content.toModel() ?.replacementRoomId From e2f0e14133f8783dd5608eb73a9021b753d5ba57 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 13 Sep 2022 23:57:41 +0200 Subject: [PATCH 11/32] Start DM - Add loading wheel while creating the room --- .../org/matrix/android/sdk/flow/FlowRoom.kt | 8 ++ .../android/sdk/api/session/room/Room.kt | 12 ++ .../sdk/api/session/room/RoomService.kt | 7 + .../room/model/LocalRoomCreationState.kt | 24 ++++ .../session/room/model/LocalRoomSummary.kt | 46 ++++++ .../database/RealmSessionStoreMigration.kt | 4 +- .../database/mapper/LocalRoomSummaryMapper.kt | 36 +++++ .../database/migration/MigrateSessionTo037.kt | 34 +++++ .../database/model/LocalRoomSummaryEntity.kt | 11 +- .../query/LocalRoomSummaryEntityQueries.kt | 8 +- .../sdk/internal/session/room/DefaultRoom.kt | 9 ++ .../session/room/DefaultRoomService.kt | 5 + .../create/CreateRoomFromLocalRoomTask.kt | 123 +++++++--------- .../room/summary/RoomSummaryDataSource.kt | 23 +++ .../DefaultCreateRoomFromLocalRoomTaskTest.kt | 131 ++++++++++++------ .../android/sdk/test/fakes/FakeMonarchy.kt | 5 + .../test/fakes/FakeRoomSummaryDataSource.kt | 37 +++++ .../home/room/detail/RoomDetailViewEvents.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 2 +- .../home/room/detail/TimelineViewModel.kt | 47 ++++--- 20 files changed, 429 insertions(+), 145 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index 8be8e83569..a6b4cc98a6 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -46,6 +47,13 @@ class FlowRoom(private val room: Room) { } } + fun liveLocalRoomSummary(): Flow> { + return room.getLocalRoomSummaryLive().asFlow() + .startWith(room.coroutineDispatchers.io) { + room.localRoomSummary().toOptional() + } + } + fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { return room.membershipService().getRoomMembersLive(queryParams).asFlow() .startWith(room.coroutineDispatchers.io) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 5d2769ac3c..8031fcaeea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.location.LocationSharingService import org.matrix.android.sdk.api.session.room.members.MembershipService +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService @@ -60,11 +61,22 @@ interface Room { */ fun getRoomSummaryLive(): LiveData> + /** + * A live [LocalRoomSummary] associated with the room. + * You can observe this summary to get dynamic data from this room. + */ + fun getLocalRoomSummaryLive(): LiveData> + /** * A current snapshot of [RoomSummary] associated with the room. */ fun roomSummary(): RoomSummary? + /** + * A current snapshot of [LocalRoomSummary] associated with the room. + */ + fun localRoomSummary(): LocalRoomSummary? + /** * Use this room as a Space, if the type is correct. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index ad8106c9c1..65383f1007 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -117,6 +118,12 @@ interface RoomService { */ fun getRoomSummaryLive(roomId: String): LiveData> + /** + * A live [LocalRoomSummary] associated with the room with id [roomId]. + * You can observe this summary to get dynamic data from this room, even if the room is not joined yet + */ + fun getLocalRoomSummaryLive(roomId: String): LiveData> + /** * Get a snapshot list of room summaries. * @return the immutable list of [RoomSummary] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt new file mode 100644 index 0000000000..4fc99225c4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +enum class LocalRoomCreationState { + NOT_CREATED, + CREATING, + FAILURE, + CREATED +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt new file mode 100644 index 0000000000..eced1dd581 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams + +/** + * This class holds some data of a local room. + * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] + */ +data class LocalRoomSummary( + /** + * The roomId of the room. + */ + val roomId: String, + /** + * The room summary of the room. + */ + val roomSummary: RoomSummary?, + /** + * The creation params attached to the room. + */ + val createRoomParams: CreateRoomParams?, + /** + * The roomId of the created room (ie. created on the server), if any. + */ + val replacementRoomId: String?, + /** + * The creation state of the room. + */ + val creationState: LocalRoomCreationState, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 0b11863864..2693ca474c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -61,7 +62,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 36L, + schemaVersion = 37L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -107,5 +108,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 34) MigrateSessionTo034(realm).perform() if (oldVersion < 35) MigrateSessionTo035(realm).perform() if (oldVersion < 36) MigrateSessionTo036(realm).perform() + if (oldVersion < 37) MigrateSessionTo037(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt new file mode 100644 index 0000000000..09cb5985f3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.mapper + +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import javax.inject.Inject + +internal class LocalRoomSummaryMapper @Inject constructor( + private val roomSummaryMapper: RoomSummaryMapper, +) { + + fun map(localRoomSummaryEntity: LocalRoomSummaryEntity): LocalRoomSummary { + return LocalRoomSummary( + roomId = localRoomSummaryEntity.roomId, + roomSummary = localRoomSummaryEntity.roomSummaryEntity?.let { roomSummaryMapper.map(it) }, + createRoomParams = localRoomSummaryEntity.createRoomParams, + replacementRoomId = localRoomSummaryEntity.replacementRoomId, + creationState = localRoomSummaryEntity.creationState + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt new file mode 100644 index 0000000000..cdb0b6c682 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo037(realm: DynamicRealm) : RealmMigrator(realm, 37) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("LocalRoomSummaryEntity") + ?.addField(LocalRoomSummaryEntityFields.REPLACEMENT_ROOM_ID, String::class.java) + ?.addField(LocalRoomSummaryEntityFields.STATE_STR, String::class.java) + ?.transform { obj -> + obj.set(LocalRoomSummaryEntityFields.STATE_STR, LocalRoomCreationState.NOT_CREATED.name) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt index fd8331e986..a978e3719d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt @@ -18,15 +18,24 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.toJSONString internal open class LocalRoomSummaryEntity( @PrimaryKey var roomId: String = "", var roomSummaryEntity: RoomSummaryEntity? = null, - private var createRoomParamsStr: String? = null + var replacementRoomId: String? = null, ) : RealmObject() { + private var stateStr: String = LocalRoomCreationState.NOT_CREATED.name + var creationState: LocalRoomCreationState + get() = LocalRoomCreationState.valueOf(stateStr) + set(value) { + stateStr = value.name + } + + private var createRoomParamsStr: String? = null var createRoomParams: CreateRoomParams? get() { return CreateRoomParams.fromJson(createRoomParamsStr) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt index 527350bedc..44730eb75d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt @@ -22,10 +22,6 @@ import io.realm.kotlin.where import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields -internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { - val query = realm.where() - if (roomId != null) { - query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId) - } - return query +internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery { + return realm.where().equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index abea2d34cd..262c111b73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.location.LocationSharingService import org.matrix.android.sdk.api.session.room.members.MembershipService +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.relation.RelationService @@ -82,6 +83,14 @@ internal class DefaultRoom( return roomSummaryDataSource.getRoomSummary(roomId) } + override fun getLocalRoomSummaryLive(): LiveData> { + return roomSummaryDataSource.getLocalRoomSummaryLive(roomId) + } + + override fun localRoomSummary(): LocalRoomSummary? { + return roomSummaryDataSource.getLocalRoomSummary(roomId) + } + override fun asSpace(): Space? { if (roomSummary()?.roomType != RoomType.SPACE) return null return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 989bcaee44..381e331540 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -106,6 +107,10 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummaryLive(roomId) } + override fun getLocalRoomSummaryLive(roomId: String): LiveData> { + return roomSummaryDataSource.getLocalRoomSummaryLive(roomId) + } + override fun getRoomSummaries( queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 246b6aa241..57ffe7fb0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -18,36 +18,22 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure -import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent -import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields -import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore -import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.awaitTransaction -import org.matrix.android.sdk.internal.util.time.Clock -import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -55,94 +41,85 @@ import javax.inject.Inject * Create a room on the server from a local room. * The configuration of the local room will be use to configure the new room. * The potential local room members will also be invited to this new room. - * - * A local tombstone event will be created to indicate that the local room has been replacing by the new one. */ internal interface CreateRoomFromLocalRoomTask : Task { data class Params(val localRoomId: String) } internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( - @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, - private val stateEventDataSource: StateEventDataSource, - private val clock: Clock, + private val roomSummaryDataSource: RoomSummaryDataSource, ) : CreateRoomFromLocalRoomTask { private val realmConfiguration get() = monarchy.realmConfiguration override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { + val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId) + ?.takeIf { it.createRoomParams != null && it.roomSummary != null } + ?: error("Invalid LocalRoomSummary for ${params.localRoomId}") + // If a room has already been created for the given local room, return the existing roomId - val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) - ?.content.toModel() - ?.replacementRoomId - - if (replacementRoomId != null) { - return replacementRoomId + if (localRoomSummary.replacementRoomId != null) { + return localRoomSummary.replacementRoomId } - var createRoomParams: CreateRoomParams? = null - var isEncrypted = false - monarchy.doWithRealm { realm -> - LocalRoomSummaryEntity.where(realm, params.localRoomId) - .findFirst() - ?.let { - createRoomParams = it.createRoomParams - isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse() - } - } - val roomId = createRoomTask.execute(createRoomParams!!) + return createRoom(localRoomSummary) + } + private suspend fun createRoom(localRoomSummary: LocalRoomSummary): String { + updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATING) + val replacementRoomId = runCatching { + createRoomTask.execute(localRoomSummary.createRoomParams!!) + }.fold( + { it }, + { + updateCreationState(roomId = localRoomSummary.roomId, LocalRoomCreationState.FAILURE) + throw it + } + ) + updateReplacementRoomId(localRoomSummary.roomId, replacementRoomId) + waitForRoomEvents(replacementRoomId, localRoomSummary.roomSummary!!) + updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATED) + return replacementRoomId + } + + /** + * Wait for all the room events before triggering the created state. + */ + private suspend fun waitForRoomEvents(replacementRoomId: String, roomSummary: RoomSummary) { try { - // Wait for all the room events before triggering the replacement room awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> realm.where(RoomSummaryEntity::class.java) - .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) - .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0) + .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, roomSummary.invitedMembersCount) } awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - EventEntity.whereRoomId(realm, roomId) + EventEntity.whereRoomId(realm, replacementRoomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) } - if (isEncrypted) { + if (roomSummary.isEncrypted) { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - EventEntity.whereRoomId(realm, roomId) + EventEntity.whereRoomId(realm, replacementRoomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) } } } catch (exception: TimeoutCancellationException) { - throw CreateRoomFailure.CreatedWithTimeout(roomId) + updateCreationState(roomSummary.roomId, LocalRoomCreationState.FAILURE) + throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId) } - - createTombstoneEvent(params, roomId) - return roomId } - /** - * Create a Tombstone event to indicate that the local room has been replaced by a new one. - */ - private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) { - val now = clock.epochMillis() - val event = Event( - type = EventType.STATE_ROOM_TOMBSTONE, - senderId = userId, - originServerTs = now, - stateKey = "", - eventId = UUID.randomUUID().toString(), - content = RoomTombstoneContent( - replacementRoomId = roomId - ).toContent() - ) - monarchy.awaitTransaction { realm -> - val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) - if (event.stateKey != null && event.type != null && event.eventId != null) { - CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply { - eventId = event.eventId - root = eventEntity - } - } + private fun updateCreationState(roomId: String, creationState: LocalRoomCreationState) { + monarchy.runTransactionSync { realm -> + LocalRoomSummaryEntity.where(realm, roomId).findFirst()?.creationState = creationState + } + } + + private fun updateReplacementRoomId(localRoomId: String, replacementRoomId: String) { + monarchy.runTransactionSync { realm -> + LocalRoomSummaryEntity.where(realm, localRoomId).findFirst()?.replacementRoomId = replacementRoomId } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index afc1d5012f..5c4ed8012b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.ResultBoundaries import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType @@ -43,7 +44,9 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.LocalRoomSummaryMapper import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.findByAlias @@ -57,6 +60,7 @@ import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val roomSummaryMapper: RoomSummaryMapper, + private val localRoomSummaryMapper: LocalRoomSummaryMapper, private val queryStringValueProcessor: QueryStringValueProcessor, ) { @@ -95,6 +99,25 @@ internal class RoomSummaryDataSource @Inject constructor( ) } + fun getLocalRoomSummary(roomId: String): LocalRoomSummary? { + return monarchy + .fetchCopyMap({ + LocalRoomSummaryEntity.where(it, roomId).findFirst() + }, { entity, _ -> + localRoomSummaryMapper.map(entity) + }) + } + + fun getLocalRoomSummaryLive(roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm -> LocalRoomSummaryEntity.where(realm, roomId) }, + { localRoomSummaryMapper.map(it) } + ) + return Transformations.map(liveData) { results -> + results.firstOrNull().toOptional() + } + } + fun getRoomSummariesLive( queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt index d3732363b5..9e34280437 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt @@ -22,21 +22,22 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.spyk import io.mockk.unmockkAll +import io.mockk.verify +import io.mockk.verifyOrder import io.realm.kotlin.where import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull import org.junit.After import org.junit.Before import org.junit.Test -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity @@ -44,29 +45,24 @@ import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate -import org.matrix.android.sdk.internal.util.time.DefaultClock import org.matrix.android.sdk.test.fakes.FakeMonarchy -import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource +import org.matrix.android.sdk.test.fakes.FakeRoomSummaryDataSource private const val A_LOCAL_ROOM_ID = "local.a-local-room-id" private const val AN_EXISTING_ROOM_ID = "an-existing-room-id" private const val A_ROOM_ID = "a-room-id" -private const val MY_USER_ID = "my-user-id" @ExperimentalCoroutinesApi internal class DefaultCreateRoomFromLocalRoomTaskTest { private val fakeMonarchy = FakeMonarchy() - private val clock = DefaultClock() private val createRoomTask = mockk() - private val fakeStateEventDataSource = FakeStateEventDataSource() + private val fakeRoomSummaryDataSource = FakeRoomSummaryDataSource() private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask( - userId = MY_USER_ID, monarchy = fakeMonarchy.instance, createRoomTask = createRoomTask, - stateEventDataSource = fakeStateEventDataSource.instance, - clock = clock + roomSummaryDataSource = fakeRoomSummaryDataSource.instance, ) @Before @@ -91,13 +87,12 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { @Test fun `given a local room id when execute then the existing room id is kept`() = runTest { // Given - givenATombstoneEvent( - Event( - roomId = A_LOCAL_ROOM_ID, - type = EventType.STATE_ROOM_TOMBSTONE, - stateKey = "", - content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent() - ) + val aCreateRoomParams = mockk(relaxed = true) + givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aCreationState = LocalRoomCreationState.CREATED, aReplacementRoomId = AN_EXISTING_ROOM_ID) + val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity( + aCreateRoomParams = aCreateRoomParams, + aCreationState = LocalRoomCreationState.CREATED, + aReplacementRoomId = AN_EXISTING_ROOM_ID ) // When @@ -105,20 +100,18 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { val result = defaultCreateRoomFromLocalRoomTask.execute(params) // Then - verifyTombstoneEvent(AN_EXISTING_ROOM_ID) + fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID) result shouldBeEqualTo AN_EXISTING_ROOM_ID + aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo AN_EXISTING_ROOM_ID + aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED } @Test fun `given a local room id when execute then it is correctly executed`() = runTest { // Given - val aCreateRoomParams = mockk() - val aLocalRoomSummaryEntity = mockk { - every { roomSummaryEntity } returns mockk(relaxed = true) - every { createRoomParams } returns aCreateRoomParams - } - givenATombstoneEvent(null) - givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity) + val aCreateRoomParams = mockk(relaxed = true) + givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null) + val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null) coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID @@ -127,32 +120,84 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { val result = defaultCreateRoomFromLocalRoomTask.execute(params) // Then - verifyTombstoneEvent(null) + fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID) // CreateRoomTask has been called with the initial CreateRoomParams coVerify { createRoomTask.execute(aCreateRoomParams) } // The resulting roomId matches the roomId returned by the createRoomTask result shouldBeEqualTo A_ROOM_ID - // A tombstone state event has been created - coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) } + // The room creation state has correctly been updated + verifyOrder { + aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING + aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATED + } + // The local room summary has been updated with the created room id + verify { aLocalRoomSummaryEntity.replacementRoomId = A_ROOM_ID } + aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo A_ROOM_ID + aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED } - private fun givenATombstoneEvent(event: Event?) { - fakeStateEventDataSource.givenGetStateEventReturns(event) + @Test + fun `given a local room id when execute with an exception then the creation state is correctly updated`() = runTest { + // Given + val aCreateRoomParams = mockk(relaxed = true) + givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null) + val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null) + + coEvery { createRoomTask.execute(any()) }.throws(mockk()) + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + tryOrNull { defaultCreateRoomFromLocalRoomTask.execute(params) } + + // Then + fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID) + // CreateRoomTask has been called with the initial CreateRoomParams + coVerify { createRoomTask.execute(aCreateRoomParams) } + // The room creation state has correctly been updated + verifyOrder { + aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING + aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.FAILURE + } + // The local room summary has been updated with the created room id + aLocalRoomSummaryEntity.replacementRoomId.shouldBeNull() + aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.FAILURE } - private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) { + private fun givenALocalRoomSummary( + aCreateRoomParams: CreateRoomParams, + aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED, + aReplacementRoomId: String? = null + ): LocalRoomSummary { + val aLocalRoomSummary = LocalRoomSummary( + roomId = A_LOCAL_ROOM_ID, + roomSummary = mockk(relaxed = true), + createRoomParams = aCreateRoomParams, + creationState = aCreationState, + replacementRoomId = aReplacementRoomId, + ) + fakeRoomSummaryDataSource.givenGetLocalRoomSummaryReturns(A_LOCAL_ROOM_ID, aLocalRoomSummary) + return aLocalRoomSummary + } + + private fun givenALocalRoomSummaryEntity( + aCreateRoomParams: CreateRoomParams, + aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED, + aReplacementRoomId: String? = null + ): LocalRoomSummaryEntity { + val aLocalRoomSummaryEntity = spyk(LocalRoomSummaryEntity( + roomId = A_LOCAL_ROOM_ID, + roomSummaryEntity = mockk(relaxed = true), + replacementRoomId = aReplacementRoomId, + ).apply { + createRoomParams = aCreateRoomParams + creationState = aCreationState + }) every { fakeMonarchy.fakeRealm.instance .where() .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID) .findFirst() - } returns localRoomSummaryEntity - } - - private fun verifyTombstoneEvent(expectedRoomId: String?) { - fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) - fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) - ?.content.toModel() - ?.replacementRoomId shouldBeEqualTo expectedRoomId + } returns aLocalRoomSummaryEntity + return aLocalRoomSummaryEntity } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index 2d501f12af..93999458c6 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -47,6 +47,11 @@ internal class FakeMonarchy { } coAnswers { firstArg().doWithRealm(fakeRealm.instance) } + coEvery { + instance.runTransactionSync(any()) + } coAnswers { + firstArg().execute(fakeRealm.instance) + } every { instance.realmConfiguration } returns mockk() } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt new file mode 100644 index 0000000000..8c857999ca --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource + +internal class FakeRoomSummaryDataSource { + + val instance: RoomSummaryDataSource = mockk() + + fun givenGetLocalRoomSummaryReturns(roomId: String?, localRoomSummary: LocalRoomSummary?) { + every { instance.getLocalRoomSummary(roomId = roomId ?: any()) } returns localRoomSummary + } + + fun verifyGetLocalRoomSummary(roomId: String) { + verify { instance.getLocalRoomSummary(roomId) } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index 3af849e965..399d5e0abe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -51,7 +51,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { object OpenRoomProfile : RoomDetailViewEvents() data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : RoomDetailViewEvents() - object ShowWaitingView : RoomDetailViewEvents() + data class ShowWaitingView(val text: String? = null) : RoomDetailViewEvents() object HideWaitingView : RoomDetailViewEvents() data class DownloadFileState( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 8b6429abb1..5eb90dde4b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -493,7 +493,7 @@ class TimelineFragment : is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference() - RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() + is RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView(it.text) RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 535a949cd3..a6513ffc4f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -83,7 +83,6 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType @@ -100,9 +99,11 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent @@ -185,6 +186,7 @@ class TimelineViewModel @AssistedInject constructor( init { // This method will take care of a null room to update the state. observeRoomSummary() + observeLocalRoomSummary() if (room == null) { timeline = null } else { @@ -617,7 +619,7 @@ class TimelineViewModel @AssistedInject constructor( } private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) { - _viewEvents.post(RoomDetailViewEvents.ShowWaitingView) + _viewEvents.post(RoomDetailViewEvents.ShowWaitingView()) viewModelScope.launch(Dispatchers.IO) { try { val widget = jitsiService.createJitsiWidget(initialState.roomId, action.withVideo) @@ -637,7 +639,7 @@ class TimelineViewModel @AssistedInject constructor( if (isJitsiWidget) { setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) } } else { - _viewEvents.post(RoomDetailViewEvents.ShowWaitingView) + _viewEvents.post(RoomDetailViewEvents.ShowWaitingView()) } session.widgetService().destroyRoomWidget(initialState.roomId, widgetId) // local echo @@ -1231,6 +1233,28 @@ class TimelineViewModel @AssistedInject constructor( } } + private fun observeLocalRoomSummary() { + if (room != null && RoomLocalEcho.isLocalEchoId(room.roomId)) { + room.flow().liveLocalRoomSummary() + .unwrap() + .map { it.creationState } + .distinctUntilChanged() + .onEach { creationState -> + when (creationState) { + LocalRoomCreationState.NOT_CREATED -> Unit + LocalRoomCreationState.CREATING -> + _viewEvents.post(RoomDetailViewEvents.ShowWaitingView(stringProvider.getString(R.string.creating_direct_room))) + LocalRoomCreationState.FAILURE -> { + _viewEvents.post(RoomDetailViewEvents.HideWaitingView) + } + LocalRoomCreationState.CREATED -> + _viewEvents.post(RoomDetailViewEvents.OpenRoom(room.localRoomSummary()?.replacementRoomId!!, true)) + } + } + .launchIn(viewModelScope) + } + } + private fun getUnreadState() { if (room == null) return combine( @@ -1322,26 +1346,11 @@ class TimelineViewModel @AssistedInject constructor( } } room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also { - onRoomTombstoneUpdated(it) + setState { copy(tombstoneEvent = it) } } } } - private var roomTombstoneHandled = false - private fun onRoomTombstoneUpdated(tombstoneEvent: Event) = withState { state -> - if (roomTombstoneHandled) return@withState - if (state.isLocalRoom()) { - // Local room has been replaced, so navigate to the new room - val roomId = tombstoneEvent.getClearContent()?.toModel() - ?.replacementRoomId - ?: return@withState - _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true)) - roomTombstoneHandled = true - } else { - setState { copy(tombstoneEvent = tombstoneEvent) } - } - } - /** * Navigates to the appropriate event (by paginating the thread timeline until the event is found * in the snapshot. The main reason for this function is to support the /relations api From 10b5e8fd042343a5736dc79d3f2f4521a550d69a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 14 Sep 2022 16:28:09 +0200 Subject: [PATCH 12/32] Changelog --- changelog.d/6970.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6970.wip diff --git a/changelog.d/6970.wip b/changelog.d/6970.wip new file mode 100644 index 0000000000..4ec53e0d53 --- /dev/null +++ b/changelog.d/6970.wip @@ -0,0 +1 @@ +Create DM room only on first message - Add a spinner when sending the first message From 3f88811590bad89dc6716679d74256d74816e6b0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 14 Sep 2022 17:09:25 +0200 Subject: [PATCH 13/32] remove unused import --- .../matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt index 8c857999ca..c7b70a3ad5 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt @@ -20,7 +20,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource internal class FakeRoomSummaryDataSource { From eac74bda0932931b7ec9d53383f813c748d8a12e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 09:37:22 +0200 Subject: [PATCH 14/32] Improve nullability check in CreateRoomFromLocalRoomTask --- .../create/CreateRoomFromLocalRoomTask.kt | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 57ffe7fb0c..2245eb8513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -21,8 +21,8 @@ import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState -import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields @@ -57,56 +57,71 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId) - ?.takeIf { it.createRoomParams != null && it.roomSummary != null } - ?: error("Invalid LocalRoomSummary for ${params.localRoomId}") + ?: error("## CreateRoomFromLocalRoomTask - Cannot retrieve LocalRoomSummary with roomId ${params.localRoomId}") // If a room has already been created for the given local room, return the existing roomId if (localRoomSummary.replacementRoomId != null) { return localRoomSummary.replacementRoomId } - return createRoom(localRoomSummary) + if (localRoomSummary.createRoomParams != null && localRoomSummary.roomSummary != null) { + return createRoom(params.localRoomId, localRoomSummary.roomSummary, localRoomSummary.createRoomParams) + } else { + error("## CreateRoomFromLocalRoomTask - Invalid LocalRoomSummary: $localRoomSummary") + } } - private suspend fun createRoom(localRoomSummary: LocalRoomSummary): String { - updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATING) + /** + * Create a room on the server for the given local room. + * + * @param localRoomId the local room identifier. + * @param localRoomSummary the RoomSummary of the local room. + * @param createRoomParams the CreateRoomParams object which was used to configure the local room. + * + * @return the identifier of the created room. + */ + private suspend fun createRoom(localRoomId: String, localRoomSummary: RoomSummary, createRoomParams: CreateRoomParams): String { + updateCreationState(localRoomId, LocalRoomCreationState.CREATING) val replacementRoomId = runCatching { - createRoomTask.execute(localRoomSummary.createRoomParams!!) + createRoomTask.execute(createRoomParams) }.fold( { it }, { - updateCreationState(roomId = localRoomSummary.roomId, LocalRoomCreationState.FAILURE) + updateCreationState(localRoomId, LocalRoomCreationState.FAILURE) throw it } ) - updateReplacementRoomId(localRoomSummary.roomId, replacementRoomId) - waitForRoomEvents(replacementRoomId, localRoomSummary.roomSummary!!) - updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATED) + updateReplacementRoomId(localRoomId, replacementRoomId) + waitForRoomEvents(replacementRoomId, localRoomSummary) + updateCreationState(localRoomId, LocalRoomCreationState.CREATED) return replacementRoomId } /** * Wait for all the room events before triggering the created state. + * + * @param replacementRoomId the identifier of the created room + * @param localRoomSummary the RoomSummary of the local room. */ - private suspend fun waitForRoomEvents(replacementRoomId: String, roomSummary: RoomSummary) { + private suspend fun waitForRoomEvents(replacementRoomId: String, localRoomSummary: RoomSummary) { try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> realm.where(RoomSummaryEntity::class.java) .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId) - .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, roomSummary.invitedMembersCount) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, localRoomSummary.invitedMembersCount) } awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> EventEntity.whereRoomId(realm, replacementRoomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) } - if (roomSummary.isEncrypted) { + if (localRoomSummary.isEncrypted) { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> EventEntity.whereRoomId(realm, replacementRoomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) } } } catch (exception: TimeoutCancellationException) { - updateCreationState(roomSummary.roomId, LocalRoomCreationState.FAILURE) + updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.FAILURE) throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId) } } From 75236e9ed05cc1b08d4d15aed3c18422b76be921 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 10:17:05 +0200 Subject: [PATCH 15/32] Start with `buildjet-2vcpu-ubuntu-2204` --- .github/workflows/post-pr.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index bf948064ed..8eff24b0c8 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -31,7 +31,7 @@ jobs: ui-tests: name: UI Tests (Synapse) needs: should-i-run - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: buildjet-2vcpu-ubuntu-2204 strategy: fail-fast: false matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ffffb2b760..c63ab593b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ env: jobs: tests: name: Runs all tests - runs-on: buildjet-4vcpu-ubuntu-2204 # for the emulator + runs-on: buildjet-2vcpu-ubuntu-2204 # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} From 874bcc117a322aecd8e5a81a1760fcc1a0ec44c7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 10:32:10 +0200 Subject: [PATCH 16/32] Fix regression on our dependency, due to merge of #6788. We do not use `android-embedded_fcm_distributor` anymore (since #7068). The code was compiling because `android-embedded_fcm_distributor` has a dependency on `firebase-messaging`. --- vector-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index ea543166fe..dacd1416fd 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -372,7 +372,7 @@ dependencies { gplayImplementation "com.google.android.gms:play-services-location:20.0.0" // UnifiedPush gplay flavor only - gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') { + gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' From 237da2ce2273019c4310213aab03d3deb478272b Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 19 Sep 2022 11:08:25 +0200 Subject: [PATCH 17/32] changed app layout flag for all_test --- .../src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index b70fcfec25..9fb7fceebf 100644 --- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -50,7 +50,7 @@ import im.vector.app.withIdlingResource import timber.log.Timber class ElementRobot( - private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(false) + private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(true) ) { fun onboarding(block: OnboardingRobot.() -> Unit) { block(OnboardingRobot()) From bf493f27aee9bc214fe60c6a2c93bc996c2fbe62 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 14:31:35 +0200 Subject: [PATCH 18/32] Revert to `buildjet-4vcpu-ubuntu-2204` --- .github/workflows/post-pr.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index 8eff24b0c8..bf948064ed 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -31,7 +31,7 @@ jobs: ui-tests: name: UI Tests (Synapse) needs: should-i-run - runs-on: buildjet-2vcpu-ubuntu-2204 + runs-on: buildjet-4vcpu-ubuntu-2204 strategy: fail-fast: false matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c63ab593b4..e260749374 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ env: jobs: tests: name: Runs all tests - runs-on: buildjet-2vcpu-ubuntu-2204 + runs-on: buildjet-4vcpu-ubuntu-2204 # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} From 830e5ffa9f5cd1d9d7c77d68390a73e5d5537b15 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:22:16 +0200 Subject: [PATCH 19/32] room summary now has constant height (#7145) --- changelog.d/7079.bugfix | 1 + .../core/utils/FirstItemUpdatedObserver.kt | 47 ++++++++++++++ .../home/room/list/RoomSummaryItem.kt | 7 ++ .../home/room/list/RoomSummaryItemFactory.kt | 12 ++-- .../room/list/RoomSummaryItemPlaceHolder.kt | 17 ++++- .../room/list/RoomSummaryListController.kt | 12 +++- .../room/list/RoomSummaryPagedController.kt | 22 +++++-- .../list/RoomSummaryPagedControllerFactory.kt | 8 ++- .../list/home/HomeFilteredRoomsController.kt | 20 +++++- .../room/list/home/HomeRoomListFragment.kt | 17 +++-- .../home/header/HomeRoomsHeadersController.kt | 64 ++++++++++--------- .../features/settings/FontScalePreferences.kt | 24 +++++-- .../features/share/IncomingShareController.kt | 1 + .../main/res/drawable/placeholder_shape_8.xml | 3 +- .../src/main/res/layout/item_recent_room.xml | 1 - vector/src/main/res/layout/item_room.xml | 2 +- .../main/res/layout/item_room_placeholder.xml | 48 ++++++++------ 17 files changed, 222 insertions(+), 84 deletions(-) create mode 100644 changelog.d/7079.bugfix create mode 100644 vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt diff --git a/changelog.d/7079.bugfix b/changelog.d/7079.bugfix new file mode 100644 index 0000000000..b63d491e4b --- /dev/null +++ b/changelog.d/7079.bugfix @@ -0,0 +1 @@ +Fixed problem when room list's scroll did jump after rooms placeholders were replaced with rooms summary items diff --git a/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt b/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt new file mode 100644 index 0000000000..25901cdf95 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.utils + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +/** + * This observer detects when item was added or moved to the first position of the adapter, while recyclerView is scrolled to the top. This is necessary + * to force recycler to scroll to the top to make such item visible, because by default it will keep items on screen, while adding new item to the top, + * outside of the viewport + * @param layoutManager - [LinearLayoutManager] of the recycler view, which displays items + * @property onItemUpdated - callback to be called, when observer detects event + */ +class FirstItemUpdatedObserver( + layoutManager: LinearLayoutManager, + private val onItemUpdated: () -> Unit +) : RecyclerView.AdapterDataObserver() { + + val layoutManager: LinearLayoutManager? by weak(layoutManager) + + override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { + if ((toPosition == 0 || fromPosition == 0) && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) { + onItemUpdated.invoke() + } + } + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == 0 && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) { + onItemUpdated.invoke() + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt index 58ae6520cf..e6d162e8c3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt @@ -103,6 +103,9 @@ abstract class RoomSummaryItem : VectorEpoxyModel(R.layo @EpoxyAttribute var showSelected: Boolean = false + @EpoxyAttribute + var useSingleLineForLastEvent: Boolean = false + override fun bind(holder: Holder) { super.bind(holder) @@ -122,6 +125,10 @@ abstract class RoomSummaryItem : VectorEpoxyModel(R.layo holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending renderSelection(holder, showSelected) holder.roomAvatarPresenceImageView.render(showPresence, userPresence) + + if (useSingleLineForLastEvent) { + holder.subtitleView.setLines(1) + } } private fun renderDisplayMode(holder: Holder) = when (displayMode) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 85879e6807..290b66e576 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -51,7 +51,8 @@ class RoomSummaryItemFactory @Inject constructor( roomChangeMembershipStates: Map, selectedRoomIds: Set, displayMode: RoomListDisplayMode, - listener: RoomListListener? + listener: RoomListListener?, + singleLineLastEvent: Boolean = false ): VectorEpoxyModel<*> { return when (roomSummary.membership) { Membership.INVITE -> { @@ -59,7 +60,7 @@ class RoomSummaryItemFactory @Inject constructor( createInvitationItem(roomSummary, changeMembershipState, listener) } else -> createRoomItem( - roomSummary, selectedRoomIds, displayMode, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked } + roomSummary, selectedRoomIds, displayMode, singleLineLastEvent, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked } ) } } @@ -118,8 +119,9 @@ class RoomSummaryItemFactory @Inject constructor( roomSummary: RoomSummary, selectedRoomIds: Set, displayMode: RoomListDisplayMode, + singleLineLastEvent: Boolean, onClick: ((RoomSummary) -> Unit)?, - onLongClick: ((RoomSummary) -> Boolean)? + onLongClick: ((RoomSummary) -> Boolean)?, ): VectorEpoxyModel<*> { val subtitle = getSearchResultSubtitle(roomSummary) val unreadCount = roomSummary.notificationCount @@ -140,7 +142,7 @@ class RoomSummaryItemFactory @Inject constructor( } else { createRoomSummaryItem( roomSummary, displayMode, subtitle, latestEventTime, typingMessage, - latestFormattedEvent, showHighlighted, showSelected, unreadCount, onClick, onLongClick + latestFormattedEvent, showHighlighted, showSelected, unreadCount, singleLineLastEvent, onClick, onLongClick ) } } @@ -155,6 +157,7 @@ class RoomSummaryItemFactory @Inject constructor( showHighlighted: Boolean, showSelected: Boolean, unreadCount: Int, + singleLineLastEvent: Boolean, onClick: ((RoomSummary) -> Unit)?, onLongClick: ((RoomSummary) -> Boolean)? ) = RoomSummaryItem_() @@ -177,6 +180,7 @@ class RoomSummaryItemFactory @Inject constructor( .unreadNotificationCount(unreadCount) .hasUnreadMessage(roomSummary.hasUnreadMessages) .hasDraft(roomSummary.userDrafts.isNotEmpty()) + .useSingleLineForLastEvent(singleLineLastEvent) .itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false } .itemClickListener { onClick?.invoke(roomSummary) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt index d4683f78a5..df191bc2ec 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt @@ -16,6 +16,8 @@ package im.vector.app.features.home.room.list +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.VectorEpoxyHolder @@ -23,5 +25,18 @@ import im.vector.app.core.epoxy.VectorEpoxyModel @EpoxyModelClass abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel(R.layout.item_room_placeholder) { - class Holder : VectorEpoxyHolder() + + @EpoxyAttribute + var useSingleLineForLastEvent: Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + if (useSingleLineForLastEvent) { + holder.subtitleView.setLines(1) + } + } + + class Holder : VectorEpoxyHolder() { + val subtitleView by bind(R.id.subtitleView) + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt index 2eb8921fd5..a2b6ed51d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt @@ -17,18 +17,26 @@ package im.vector.app.features.home.room.list import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.settings.FontScalePreferences import org.matrix.android.sdk.api.session.room.model.RoomSummary class RoomSummaryListController( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val displayMode: RoomListDisplayMode + private val displayMode: RoomListDisplayMode, + fontScalePreferences: FontScalePreferences ) : CollapsableTypedEpoxyController>() { var listener: RoomListListener? = null + private val shouldUseSingleLine: Boolean + + init { + val fontScale = fontScalePreferences.getResolvedFontScaleValue() + shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE + } override fun buildModels(data: List?) { data?.forEach { - add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener)) + add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener, shouldUseSingleLine)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt index 445438eec9..10d7ef425c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -20,18 +20,26 @@ import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.settings.FontScalePreferences import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary class RoomSummaryPagedController( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val displayMode: RoomListDisplayMode + private val displayMode: RoomListDisplayMode, + fontScalePreferences: FontScalePreferences ) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() ), CollapsableControllerExtension { var listener: RoomListListener? = null + private val shouldUseSingleLine: Boolean + + init { + val fontScale = fontScalePreferences.getResolvedFontScaleValue() + shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE + } var roomChangeMembershipStates: Map? = null set(value) { @@ -57,8 +65,14 @@ class RoomSummaryPagedController( } override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { - // for place holder if enabled - item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } - return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener) + return if (item == null) { + val host = this + RoomSummaryItemPlaceHolder_().apply { + id(currentPosition) + useSingleLineForLastEvent(host.shouldUseSingleLine) + } + } else { + roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener, shouldUseSingleLine) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt index f72698048d..c5edd9c063 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt @@ -17,18 +17,20 @@ package im.vector.app.features.home.room.list import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.settings.FontScalePreferences import javax.inject.Inject class RoomSummaryPagedControllerFactory @Inject constructor( - private val roomSummaryItemFactory: RoomSummaryItemFactory + private val roomSummaryItemFactory: RoomSummaryItemFactory, + private val fontScalePreferences: FontScalePreferences ) { fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController { - return RoomSummaryPagedController(roomSummaryItemFactory, displayMode) + return RoomSummaryPagedController(roomSummaryItemFactory, displayMode, fontScalePreferences) } fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController { - return RoomSummaryListController(roomSummaryItemFactory, displayMode) + return RoomSummaryListController(roomSummaryItemFactory, displayMode, fontScalePreferences) } fun createSuggestedRoomListController(): SuggestedRoomListController { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt index ae0f9d328f..ebf322dc23 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt @@ -24,12 +24,14 @@ import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.home.room.list.RoomSummaryItemFactory import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_ +import im.vector.app.features.settings.FontScalePreferences import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject class HomeFilteredRoomsController @Inject constructor( private val roomSummaryItemFactory: RoomSummaryItemFactory, + fontScalePreferences: FontScalePreferences ) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -47,6 +49,13 @@ class HomeFilteredRoomsController @Inject constructor( private var emptyStateData: StateView.State.Empty? = null private var currentState: StateView.State = StateView.State.Content + private val shouldUseSingleLine: Boolean + + init { + val fontScale = fontScalePreferences.getResolvedFontScaleValue() + shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE + } + override fun addModels(models: List>) { if (models.isEmpty() && emptyStateData != null) { emptyStateData?.let { emptyState -> @@ -67,7 +76,14 @@ class HomeFilteredRoomsController @Inject constructor( } override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { - item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } - return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener) + return if (item == null) { + val host = this + RoomSummaryItemPlaceHolder_().apply { + id(currentPosition) + useSingleLineForLastEvent(host.shouldUseSingleLine) + } + } else { + roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener, shouldUseSingleLine) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 4ae2c7d514..88bbc6986f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -24,7 +24,6 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -36,6 +35,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.core.utils.FirstItemUpdatedObserver import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.list.RoomListAnimator @@ -66,6 +66,7 @@ class HomeRoomListFragment : private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel() private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel private var concatAdapter = ConcatAdapter() + private lateinit var firstItemObserver: FirstItemUpdatedObserver private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var stateRestorer: LayoutManagerStateRestorer @@ -130,6 +131,9 @@ class HomeRoomListFragment : private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) + firstItemObserver = FirstItemUpdatedObserver(layoutManager) { + layoutManager.scrollToPosition(0) + } stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() @@ -158,14 +162,7 @@ class HomeRoomListFragment : views.roomListView.adapter = concatAdapter - // we need to force scroll when recents/filter tabs are added to make them visible - concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - layoutManager.scrollToPosition(0) - } - } - }) + concatAdapter.registerAdapterDataObserver(firstItemObserver) } override fun invalidate() = withState(roomListViewModel) { state -> @@ -233,6 +230,8 @@ class HomeRoomListFragment : roomsController.listener = null + concatAdapter.unregisterAdapterDataObserver(firstItemObserver) + super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt index f7c9eccd0b..56cccd9c36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.list.home.header import android.content.res.Resources import android.util.TypedValue -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.epoxy.Carousel import com.airbnb.epoxy.CarouselModelBuilder import com.airbnb.epoxy.EpoxyController @@ -27,6 +27,7 @@ import com.airbnb.epoxy.carousel import com.google.android.material.color.MaterialColors import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.FirstItemUpdatedObserver import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.RoomListListener import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -47,22 +48,7 @@ class HomeRoomsHeadersController @Inject constructor( private var carousel: Carousel? = null - private val carouselAdapterObserver = object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { - if (toPosition == 0 || fromPosition == 0) { - carousel?.post { - carousel?.layoutManager?.scrollToPosition(0) - } - } - super.onItemRangeMoved(fromPosition, toPosition, itemCount) - } - - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { - carousel?.layoutManager?.scrollToPosition(0) - } - } - } + private var carouselAdapterObserver: FirstItemUpdatedObserver? = null private val recentsHPadding = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, @@ -113,25 +99,16 @@ class HomeRoomsHeadersController @Inject constructor( ) onBind { _, view, _ -> host.carousel = view + host.unsubscribeAdapterObserver() + host.subscribeAdapterObserver() val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background) view.setBackgroundColor(colorSurface) - - try { - view.adapter?.registerAdapterDataObserver(host.carouselAdapterObserver) - } catch (e: IllegalStateException) { - // do nothing - } } - onUnbind { _, view -> + onUnbind { _, _ -> host.carousel = null - - try { - view.adapter?.unregisterAdapterDataObserver(host.carouselAdapterObserver) - } catch (e: IllegalStateException) { - // do nothing - } + host.unsubscribeAdapterObserver() } withModelsFrom(recents) { roomSummary -> @@ -150,6 +127,33 @@ class HomeRoomsHeadersController @Inject constructor( } } + private fun unsubscribeAdapterObserver() { + carouselAdapterObserver?.let { observer -> + try { + carousel?.adapter?.unregisterAdapterDataObserver(observer) + carouselAdapterObserver = null + } catch (e: IllegalStateException) { + // do nothing + } + } + } + + private fun subscribeAdapterObserver() { + (carousel?.layoutManager as? LinearLayoutManager)?.let { layoutManager -> + carouselAdapterObserver = FirstItemUpdatedObserver(layoutManager) { + carousel?.post { + layoutManager.scrollToPosition(0) + } + }.also { observer -> + try { + carousel?.adapter?.registerAdapterDataObserver(observer) + } catch (e: IllegalStateException) { + // do nothing + } + } + } + } + private fun addRoomFilterHeaderItem( filterChangedListener: ((HomeRoomFilter) -> Unit)?, filtersList: List, diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt index 292d0107ba..34862adc4f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt @@ -57,6 +57,16 @@ interface FontScalePreferences { * @return list of values */ fun getAvailableScales(): List + + companion object { + const val SCALE_TINY = 0.70f + const val SCALE_SMALL = 0.85f + const val SCALE_NORMAL = 1.00f + const val SCALE_LARGE = 1.15f + const val SCALE_LARGER = 1.30f + const val SCALE_LARGEST = 1.45f + const val SCALE_HUGE = 1.60f + } } /** @@ -73,13 +83,13 @@ class FontScalePreferencesImpl @Inject constructor( } private val fontScaleValues = listOf( - FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny), - FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small), - FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal), - FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large), - FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger), - FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest), - FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge) + FontScaleValue(0, "FONT_SCALE_TINY", FontScalePreferences.SCALE_TINY, R.string.tiny), + FontScaleValue(1, "FONT_SCALE_SMALL", FontScalePreferences.SCALE_SMALL, R.string.small), + FontScaleValue(2, "FONT_SCALE_NORMAL", FontScalePreferences.SCALE_NORMAL, R.string.normal), + FontScaleValue(3, "FONT_SCALE_LARGE", FontScalePreferences.SCALE_LARGE, R.string.large), + FontScaleValue(4, "FONT_SCALE_LARGER", FontScalePreferences.SCALE_LARGER, R.string.larger), + FontScaleValue(5, "FONT_SCALE_LARGEST", FontScalePreferences.SCALE_LARGEST, R.string.largest), + FontScaleValue(6, "FONT_SCALE_HUGE", FontScalePreferences.SCALE_HUGE, R.string.huge) ) private val normalFontScaleValue = fontScaleValues[2] diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt index 6eede93143..0c556192ac 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt @@ -60,6 +60,7 @@ class IncomingShareController @Inject constructor( roomSummary, data.selectedRoomIds, RoomListDisplayMode.FILTERED, + singleLineLastEvent = false, callback?.let { it::onRoomClicked }, callback?.let { it::onRoomLongClicked } ) diff --git a/vector/src/main/res/drawable/placeholder_shape_8.xml b/vector/src/main/res/drawable/placeholder_shape_8.xml index 503389788d..4e015d4a56 100644 --- a/vector/src/main/res/drawable/placeholder_shape_8.xml +++ b/vector/src/main/res/drawable/placeholder_shape_8.xml @@ -2,10 +2,9 @@ - - \ No newline at end of file + diff --git a/vector/src/main/res/layout/item_recent_room.xml b/vector/src/main/res/layout/item_recent_room.xml index b2d311d328..7feb8f0d16 100644 --- a/vector/src/main/res/layout/item_recent_room.xml +++ b/vector/src/main/res/layout/item_recent_room.xml @@ -5,7 +5,6 @@ android:id="@+id/recentRoot" android:layout_width="84dp" android:layout_height="wrap_content" - android:background="?vctr_toolbar_background" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground" diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index ab0af18acb..a94cc0738b 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -190,7 +190,7 @@ android:layout_marginTop="3dp" android:layout_marginEnd="8dp" android:ellipsize="end" - android:maxLines="2" + android:lines="2" android:textAlignment="viewStart" android:textColor="?vctr_content_secondary" app:layout_constraintEnd_toEndOf="parent" diff --git a/vector/src/main/res/layout/item_room_placeholder.xml b/vector/src/main/res/layout/item_room_placeholder.xml index ea264f2668..fa1a83c2a1 100644 --- a/vector/src/main/res/layout/item_room_placeholder.xml +++ b/vector/src/main/res/layout/item_room_placeholder.xml @@ -16,7 +16,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginTop="12dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -29,23 +29,20 @@ - - - - - + + + From 3c68222fd70c47b9f544707b7db9834a0cee2cb2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 16 Sep 2022 09:23:14 +0200 Subject: [PATCH 20/32] Do not save local room into recent rooms --- .../internal/session/user/accountdata/UpdateBreadcrumbsTask.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index c4ea029cbb..a66a8540e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.internal.database.model.BreadcrumbsEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.di.SessionDatabase @@ -41,6 +42,8 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( ) : UpdateBreadcrumbsTask { override suspend fun execute(params: UpdateBreadcrumbsTask.Params) { + // Do not add local rooms to the recent rooms list as they should not be known by the server + if (RoomLocalEcho.isLocalEchoId(params.newTopRoomId)) return val newBreadcrumbs = // Get the breadcrumbs entity, if any monarchy.fetchCopied { BreadcrumbsEntity.get(it) } From 14d2aec506793794a39566b1b2c62c363914a34e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 16 Sep 2022 09:44:23 +0200 Subject: [PATCH 21/32] Start DM - Handle the local rooms within the new AppLayout --- .../room/list/home/HomeRoomListFragment.kt | 6 ++++++ .../room/list/home/HomeRoomListViewModel.kt | 21 +++++++++++++++++++ .../room/list/home/HomeRoomListViewState.kt | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 88bbc6986f..debcc101cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -40,6 +40,7 @@ import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.list.RoomListAnimator import im.vector.app.features.home.room.list.RoomListListener +import im.vector.app.features.home.room.list.RoomListViewState import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel @@ -98,6 +99,11 @@ class HomeRoomListFragment : is HomeRoomListViewEvents.Done -> Unit } } + + roomListViewModel.onEach(HomeRoomListViewState::localRoomIds) { + // Local rooms should not exist anymore when the room list is shown + roomListViewModel.deleteLocalRooms(it) + } } private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index e06815b5fd..c26783ab6c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter @@ -60,6 +61,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic @@ -103,6 +105,7 @@ class HomeRoomListViewModel @AssistedInject constructor( observeRecents() observeFilterTabs() observeRooms() + observeLocalRooms() } private fun observeInvites() { @@ -261,6 +264,16 @@ class HomeRoomListViewModel @AssistedInject constructor( }.launchIn(viewModelScope) } + private fun observeLocalRooms() { + session + .flow() + .liveRoomSummaries(roomSummaryQueryParams { + roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) + }) + .map { page -> page.map { it.roomId } } + .setOnEach { copy(localRoomIds = it) } + } + private fun emitEmptyState() { viewModelScope.launch { val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace()) @@ -349,6 +362,14 @@ class HomeRoomListViewModel @AssistedInject constructor( return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() } + fun deleteLocalRooms(roomsIds: Iterable) { + viewModelScope.launch { + roomsIds.forEach { + session.roomService().deleteLocalRoom(it) + } + } + } + private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState { _viewEvents.post(HomeRoomListViewEvents.SelectRoom(action.roomSummary, false)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt index 8647054f3d..2c0b6a63be 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt @@ -26,5 +26,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary data class HomeRoomListViewState( val state: StateView.State = StateView.State.Content, val headersData: RoomsHeadersData = RoomsHeadersData(), - val roomsLivePagedList: LiveData>? = null + val roomsLivePagedList: LiveData>? = null, + val localRoomIds: List = emptyList() ) : MavericksState From c28271dd8b9118c6624a75c7ec24b33ca6270cf1 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 16 Sep 2022 10:06:07 +0200 Subject: [PATCH 22/32] Add changelog --- changelog.d/7153.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7153.wip diff --git a/changelog.d/7153.wip b/changelog.d/7153.wip new file mode 100644 index 0000000000..fd12a4197b --- /dev/null +++ b/changelog.d/7153.wip @@ -0,0 +1 @@ +Create DM room only on first message - Handle the local rooms within the new AppLayout From df3fd6f69175982ed6f3bcb54b605c5f776d099e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 16 Sep 2022 10:08:16 +0200 Subject: [PATCH 23/32] Remove unused import --- .../app/features/home/room/list/home/HomeRoomListFragment.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index debcc101cf..1826b58e26 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -40,7 +40,6 @@ import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.list.RoomListAnimator import im.vector.app.features.home.room.list.RoomListListener -import im.vector.app.features.home.room.list.RoomListViewState import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel From 8999b40c1af5f6a9e11868f2b1169c487007697c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 09:58:00 +0200 Subject: [PATCH 24/32] Add action for local rooms deletion --- .../features/home/room/list/RoomListAction.kt | 1 + .../home/room/list/RoomListFragment.kt | 11 ++++--- .../home/room/list/RoomListViewModel.kt | 32 ++++++++----------- .../home/room/list/RoomListViewState.kt | 1 - .../home/room/list/home/HomeRoomListAction.kt | 1 + .../room/list/home/HomeRoomListFragment.kt | 12 ++++--- .../room/list/home/HomeRoomListViewModel.kt | 32 ++++++++----------- .../room/list/home/HomeRoomListViewState.kt | 1 - 8 files changed, 42 insertions(+), 49 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index e6b6b34503..aa982741f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -31,4 +31,5 @@ sealed class RoomListAction : VectorViewModelAction { data class LeaveRoom(val roomId: String) : RoomListAction() data class JoinSuggestedRoom(val roomId: String, val viaServers: List?) : RoomListAction() data class ShowRoomDetails(val roomId: String, val viaServers: List?) : RoomListAction() + object DeleteAllLocalRoom : RoomListAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 2c876273ea..9591048725 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -149,10 +149,13 @@ class RoomListFragment : (it.contentEpoxyController as? RoomSummaryPagedController)?.roomChangeMembershipStates = ms } } - roomListViewModel.onEach(RoomListViewState::localRoomIds) { - // Local rooms should not exist anymore when the room list is shown - roomListViewModel.deleteLocalRooms(it) - } + } + + override fun onStart() { + super.onStart() + + // Local rooms should not exist anymore when the room list is shown + roomListViewModel.handle(RoomListAction.DeleteAllLocalRoom) } private fun refreshCollapseStates() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 8283447a4d..74b55d435d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -97,7 +97,6 @@ class RoomListViewModel @AssistedInject constructor( init { observeMembershipChanges() - observeLocalRooms() spaceStateHandler.getSelectedSpaceFlow() .distinctUntilChanged() @@ -125,16 +124,6 @@ class RoomListViewModel @AssistedInject constructor( } } - private fun observeLocalRooms() { - session - .flow() - .liveRoomSummaries(roomSummaryQueryParams { - roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) - }) - .map { page -> page.map { it.roomId } } - .setOnEach { copy(localRoomIds = it) } - } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val roomListSectionBuilder = RoomListSectionBuilder( @@ -166,6 +155,7 @@ class RoomListViewModel @AssistedInject constructor( is RoomListAction.ToggleSection -> handleToggleSection(action.section) is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action) is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action) + RoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms() } } @@ -173,14 +163,6 @@ class RoomListViewModel @AssistedInject constructor( return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() } - fun deleteLocalRooms(roomsIds: Iterable) { - viewModelScope.launch { - roomsIds.forEach { - session.roomService().deleteLocalRoom(it) - } - } - } - // PRIVATE METHODS ***************************************************************************** private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState { @@ -338,4 +320,16 @@ class RoomListViewModel @AssistedInject constructor( _viewEvents.post(value) } } + + private fun handleDeleteLocalRooms() { + val localRoomIds = session.roomService() + .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) }) + .map { it.roomId } + + viewModelScope.launch { + localRoomIds.forEach { + session.roomService().deleteLocalRoom(it) + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt index 5f62cba948..d897225fd6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt @@ -31,7 +31,6 @@ data class RoomListViewState( val asyncSuggestedRooms: Async> = Uninitialized, val currentUserName: String? = null, val asyncSelectedSpace: Async = Uninitialized, - val localRoomIds: List = emptyList() ) : MavericksState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt index b7ade559da..5760874812 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt @@ -27,4 +27,5 @@ sealed class HomeRoomListAction : VectorViewModelAction { data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction() data class LeaveRoom(val roomId: String) : HomeRoomListAction() data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction() + object DeleteAllLocalRoom : HomeRoomListAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 1826b58e26..829634259a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -83,6 +83,13 @@ class HomeRoomListFragment : setupRecyclerView() } + override fun onStart() { + super.onStart() + + // Local rooms should not exist anymore when the room list is shown + roomListViewModel.handle(HomeRoomListAction.DeleteAllLocalRoom) + } + private fun setupObservers() { sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java] sharedQuickActionsViewModel @@ -98,11 +105,6 @@ class HomeRoomListFragment : is HomeRoomListViewEvents.Done -> Unit } } - - roomListViewModel.onEach(HomeRoomListViewState::localRoomIds) { - // Local rooms should not exist anymore when the room list is shown - roomListViewModel.deleteLocalRooms(it) - } } private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index c26783ab6c..35b2f02917 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -105,7 +105,6 @@ class HomeRoomListViewModel @AssistedInject constructor( observeRecents() observeFilterTabs() observeRooms() - observeLocalRooms() } private fun observeInvites() { @@ -264,16 +263,6 @@ class HomeRoomListViewModel @AssistedInject constructor( }.launchIn(viewModelScope) } - private fun observeLocalRooms() { - session - .flow() - .liveRoomSummaries(roomSummaryQueryParams { - roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) - }) - .map { page -> page.map { it.roomId } } - .setOnEach { copy(localRoomIds = it) } - } - private fun emitEmptyState() { viewModelScope.launch { val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace()) @@ -342,6 +331,7 @@ class HomeRoomListViewModel @AssistedInject constructor( is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is HomeRoomListAction.ToggleTag -> handleToggleTag(action) is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action.filter) + HomeRoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms() } } @@ -362,14 +352,6 @@ class HomeRoomListViewModel @AssistedInject constructor( return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() } - fun deleteLocalRooms(roomsIds: Iterable) { - viewModelScope.launch { - roomsIds.forEach { - session.roomService().deleteLocalRoom(it) - } - } - } - private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState { _viewEvents.post(HomeRoomListViewEvents.SelectRoom(action.roomSummary, false)) } @@ -420,6 +402,18 @@ class HomeRoomListViewModel @AssistedInject constructor( } } + private fun handleDeleteLocalRooms() = withState { + val localRoomIds = session.roomService() + .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) }) + .map { it.roomId } + + viewModelScope.launch { + localRoomIds.forEach { + session.roomService().deleteLocalRoom(it) + } + } + } + private fun String.otherTag(): String? { return when (this) { RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt index 2c0b6a63be..95625bc4b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt @@ -27,5 +27,4 @@ data class HomeRoomListViewState( val state: StateView.State = StateView.State.Content, val headersData: RoomsHeadersData = RoomsHeadersData(), val roomsLivePagedList: LiveData>? = null, - val localRoomIds: List = emptyList() ) : MavericksState From 648498e2de4e8af16b8a576c576c3f6737771812 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 15:22:17 +0200 Subject: [PATCH 25/32] Move local room check from UpdateBreadcrumbsTask to RoomService --- .../android/sdk/internal/session/room/DefaultRoomService.kt | 6 +++++- .../session/user/accountdata/UpdateBreadcrumbsTask.kt | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 989bcaee44..dd945716a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount @@ -173,7 +174,10 @@ internal class DefaultRoomService @Inject constructor( } override suspend fun onRoomDisplayed(roomId: String) { - updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId)) + // Do not add local rooms to the recent rooms list as they should not be known by the server + if (!RoomLocalEcho.isLocalEchoId(roomId)) { + updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId)) + } } override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index a66a8540e7..c4ea029cbb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.internal.database.model.BreadcrumbsEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.di.SessionDatabase @@ -42,8 +41,6 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( ) : UpdateBreadcrumbsTask { override suspend fun execute(params: UpdateBreadcrumbsTask.Params) { - // Do not add local rooms to the recent rooms list as they should not be known by the server - if (RoomLocalEcho.isLocalEchoId(params.newTopRoomId)) return val newBreadcrumbs = // Get the breadcrumbs entity, if any monarchy.fetchCopied { BreadcrumbsEntity.get(it) } From 5e504942cad4e8182ce3cd23e277e43692c9a36b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 15:49:44 +0200 Subject: [PATCH 26/32] Delete the local read receipts when deleting the local rooms --- .../internal/database/query/ReadReceiptEntityQueries.kt | 5 +++++ .../internal/session/room/delete/DeleteLocalRoomTask.kt | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt index b180c06e2c..170814d3f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt @@ -33,6 +33,11 @@ internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: Strin .equalTo(ReadReceiptEntityFields.USER_ID, userId) } +internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery { + return realm.where() + .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId) +} + internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity { return ReadReceiptEntity().apply { this.primaryKey = "${roomId}_$userId" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt index 49951d2da0..a60c7e6a27 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -22,12 +22,15 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity +import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereInRoom import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params @@ -50,6 +53,12 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( if (RoomLocalEcho.isLocalEchoId(roomId)) { monarchy.awaitTransaction { realm -> Timber.i("## DeleteLocalRoomTask - delete local room id $roomId") + ReadReceiptsSummaryEntity.whereInRoom(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptsSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + ReadReceiptEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() From 0e45494c11e35e9f49c6a6e794548c6be1855bff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 16:26:38 +0200 Subject: [PATCH 27/32] Comment out `continue-on-error: true` It does not mark the build as failed. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e260749374..cda0bd5e31 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: - name: Run all the codecoverage tests at once id: tests uses: reactivecircus/android-emulator-runner@v2 - continue-on-error: true + # continue-on-error: true with: api-level: 28 arch: x86 From 0c28384ece527f78f8fe86bed89d47e990f05a9a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 18:12:19 +0200 Subject: [PATCH 28/32] Create AVD and generate snapshot for caching. Also force AVD creation when no cache hit --- .github/workflows/tests.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cda0bd5e31..07eeec5643 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,6 +14,9 @@ jobs: tests: name: Runs all tests runs-on: buildjet-4vcpu-ubuntu-2204 + strategy: + matrix: + api-level: [28] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} @@ -36,18 +39,39 @@ jobs: httpPort: 8080 disableRateLimiting: true public_baseurl: "http://10.0.2.2:8080/" + + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + arch: x86 + profile: Nexus 5X + force-avd-creation: true # Is set to false in the doc https://github.com/ReactiveCircus/android-emulator-runner + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: echo "Generated AVD snapshot for caching." + - name: Run all the codecoverage tests at once - id: tests uses: reactivecircus/android-emulator-runner@v2 # continue-on-error: true with: - api-level: 28 + api-level: ${{ matrix.api-level }} arch: x86 profile: Nexus 5X force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - emulator-build: 7425822 + # emulator-build: 7425822 script: | ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES From aa010dedff4e9070a559410eaa4fe9d896cd3699 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Sep 2022 18:36:01 +0200 Subject: [PATCH 29/32] Try to upload integration test report log --- .github/workflows/tests.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 07eeec5643..fb8e3080ae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,6 +95,15 @@ jobs: ### ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ### ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES + - name: Upload Integration Test Report Log + uses: actions/upload-artifact@v3 + if: always() + with: + name: integration-test-error-results + path: | + */build/outputs/androidTest-results/connected/ + */build/reports/androidTests/connected/ + # we may have failed a previous step and retried, that's OK - name: Publish results to Sonar env: From 6da6f6a7f4bec96ceafcb8b21fab28b16a3fa1f9 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 19 Sep 2022 22:34:56 +0200 Subject: [PATCH 30/32] add qr code option to home screen menu (#7177) --- .../java/im/vector/app/features/home/HomeActivity.kt | 9 +++++++++ vector/src/main/res/menu/menu_new_home.xml | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index dd27b5550c..8fb73d6571 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -84,6 +84,7 @@ import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils +import im.vector.app.features.usercode.UserCodeActivity import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -634,10 +635,18 @@ class HomeActivity : launchInviteFriends() true } + R.id.menu_home_qr -> { + launchQrCode() + true + } else -> false } } + private fun launchQrCode() { + startActivity(UserCodeActivity.newIntent(this, sharedActionViewModel.session.myUserId)) + } + private fun launchInviteFriends() { activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink -> analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends)) diff --git a/vector/src/main/res/menu/menu_new_home.xml b/vector/src/main/res/menu/menu_new_home.xml index 6cd52e5608..2292480bac 100644 --- a/vector/src/main/res/menu/menu_new_home.xml +++ b/vector/src/main/res/menu/menu_new_home.xml @@ -12,6 +12,11 @@ android:title="@string/invite_friends" app:showAsAction="never" /> + + - From b4f730205757c6011c1f892df4921839ef5d799c Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 19 Sep 2022 22:35:15 +0200 Subject: [PATCH 31/32] release notes screen now properly shown on update to a version with app layout labs flag enabled by default (#7175) --- .../features/home/HomeActivityViewModel.kt | 22 ++++++++++--------- .../release/ReleaseNotesPreferencesStore.kt | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index cbe531ea71..a08298e402 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -119,17 +119,19 @@ class HomeActivityViewModel @AssistedInject constructor( } private fun observeReleaseNotes() = withState { state -> - // we don't want to show release notes for new users or after relogin - if (state.authenticationDescription == null && vectorPreferences.isNewAppLayoutEnabled()) { - releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown -> - if (!isAppLayoutOnboardingShown) { - _viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes) + if (vectorPreferences.isNewAppLayoutEnabled()) { + // we don't want to show release notes for new users or after relogin + if (state.authenticationDescription == null) { + releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown -> + if (!isAppLayoutOnboardingShown) { + _viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes) + } + }.launchIn(viewModelScope) + } else { + // we assume that users which came from auth flow either have seen updates already (relogin) or don't need them (new user) + viewModelScope.launch { + releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true) } - }.launchIn(viewModelScope) - } else { - // we assume that users which came from auth flow either have seen updates already (relogin) or don't need them (new user) - viewModelScope.launch { - releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt index cefe107905..3320bdf314 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt @@ -34,7 +34,7 @@ class ReleaseNotesPreferencesStore @Inject constructor( private val context: Context ) { - private val isAppLayoutOnboardingShown = booleanPreferencesKey("SETTINGS_APP_LAYOUT_ONBOARDING_SHOWN") + private val isAppLayoutOnboardingShown = booleanPreferencesKey("SETTINGS_APP_LAYOUT_ONBOARDING_DISPLAYED") val appLayoutOnboardingShown: Flow = context.dataStore.data .map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() } From d8ff688e76cf29a99f2649142a4a3d5e942a9e92 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Sep 2022 09:51:22 +0200 Subject: [PATCH 32/32] Fix typo in changelog. Co-authored-by: manuroe --- changelog.d/7108.misc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/7108.misc b/changelog.d/7108.misc index d4b15a0222..165bd52e57 100644 --- a/changelog.d/7108.misc +++ b/changelog.d/7108.misc @@ -1 +1 @@ -Move some GitHub action to buildjet runner, and remove the second attempt to run integration tests. +Move some GitHub actions to buildjet runners, and remove the second attempt to run integration tests.