Merge branch 'develop' into feature/ons/device_manager_security_sessions
This commit is contained in:
commit
45cf7dcd63
2
.github/workflows/post-pr.yml
vendored
2
.github/workflows/post-pr.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
|||||||
ui-tests:
|
ui-tests:
|
||||||
name: UI Tests (Synapse)
|
name: UI Tests (Synapse)
|
||||||
needs: should-i-run
|
needs: should-i-run
|
||||||
runs-on: macos-latest
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
75
.github/workflows/tests.yml
vendored
75
.github/workflows/tests.yml
vendored
@ -13,7 +13,10 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Runs all tests
|
name: Runs all tests
|
||||||
runs-on: macos-latest # for the emulator
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
api-level: [28]
|
||||||
# Allow all jobs on main and develop. Just one per PR.
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
concurrency:
|
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) }}
|
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,40 +39,70 @@ jobs:
|
|||||||
httpPort: 8080
|
httpPort: 8080
|
||||||
disableRateLimiting: true
|
disableRateLimiting: true
|
||||||
public_baseurl: "http://10.0.2.2:8080/"
|
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
|
- name: Run all the codecoverage tests at once
|
||||||
id: tests
|
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
continue-on-error: true
|
# continue-on-error: true
|
||||||
with:
|
with:
|
||||||
api-level: 28
|
api-level: ${{ matrix.api-level }}
|
||||||
arch: x86
|
arch: x86
|
||||||
profile: Nexus 5X
|
profile: Nexus 5X
|
||||||
force-avd-creation: false
|
force-avd-creation: false
|
||||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||||
disable-animations: true
|
disable-animations: true
|
||||||
emulator-build: 7425822
|
# emulator-build: 7425822
|
||||||
script: |
|
script: |
|
||||||
./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
|
./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew generateCoverageReport $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)
|
### - name: Run all the codecoverage tests at once (retry if emulator failed)
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
### uses: reactivecircus/android-emulator-runner@v2
|
||||||
if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded.
|
### 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: Upload Integration Test Report Log
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
with:
|
with:
|
||||||
api-level: 28
|
name: integration-test-error-results
|
||||||
arch: x86
|
path: |
|
||||||
profile: Nexus 5X
|
*/build/outputs/androidTest-results/connected/
|
||||||
force-avd-creation: false
|
*/build/reports/androidTests/connected/
|
||||||
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
|
# we may have failed a previous step and retried, that's OK
|
||||||
- name: Publish results to Sonar
|
- name: Publish results to Sonar
|
||||||
|
1
changelog.d/6970.wip
Normal file
1
changelog.d/6970.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
Create DM room only on first message - Add a spinner when sending the first message
|
1
changelog.d/7079.bugfix
Normal file
1
changelog.d/7079.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fixed problem when room list's scroll did jump after rooms placeholders were replaced with rooms summary items
|
1
changelog.d/7108.misc
Normal file
1
changelog.d/7108.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Move some GitHub actions to buildjet runners, and remove the second attempt to run integration tests.
|
1
changelog.d/7153.wip
Normal file
1
changelog.d/7153.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
Create DM room only on first message - Handle the local rooms within the new AppLayout
|
1
changelog.d/7166.misc
Normal file
1
changelog.d/7166.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
New App Layout is now enabled by default! Go to the Settings > Labs to toggle this
|
@ -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.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
|
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.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.ReadReceipt
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
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.RoomSummary
|
||||||
@ -46,6 +47,13 @@ class FlowRoom(private val room: Room) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveLocalRoomSummary(): Flow<Optional<LocalRoomSummary>> {
|
||||||
|
return room.getLocalRoomSummaryLive().asFlow()
|
||||||
|
.startWith(room.coroutineDispatchers.io) {
|
||||||
|
room.localRoomSummary().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>> {
|
||||||
return room.membershipService().getRoomMembersLive(queryParams).asFlow()
|
return room.membershipService().getRoomMembersLive(queryParams).asFlow()
|
||||||
.startWith(room.coroutineDispatchers.io) {
|
.startWith(room.coroutineDispatchers.io) {
|
||||||
|
@ -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.crypto.RoomCryptoService
|
||||||
import org.matrix.android.sdk.api.session.room.location.LocationSharingService
|
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.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.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
||||||
@ -60,11 +61,22 @@ interface Room {
|
|||||||
*/
|
*/
|
||||||
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A live [LocalRoomSummary] associated with the room.
|
||||||
|
* You can observe this summary to get dynamic data from this room.
|
||||||
|
*/
|
||||||
|
fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A current snapshot of [RoomSummary] associated with the room.
|
* A current snapshot of [RoomSummary] associated with the room.
|
||||||
*/
|
*/
|
||||||
fun roomSummary(): RoomSummary?
|
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.
|
* Use this room as a Space, if the type is correct.
|
||||||
*/
|
*/
|
||||||
|
@ -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.identity.model.SignInvitationResult
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
|
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.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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
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.RoomSummary
|
||||||
@ -117,6 +118,12 @@ interface RoomService {
|
|||||||
*/
|
*/
|
||||||
fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>>
|
fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<Optional<LocalRoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a snapshot list of room summaries.
|
* Get a snapshot list of room summaries.
|
||||||
* @return the immutable list of [RoomSummary]
|
* @return the immutable list of [RoomSummary]
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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,
|
||||||
|
)
|
@ -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.MigrateSessionTo034
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
|
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.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.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -61,7 +62,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 36L,
|
schemaVersion = 37L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
@ -107,5 +108,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
if (oldVersion < 34) MigrateSessionTo034(realm).perform()
|
||||||
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
|
||||||
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
|
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
|
||||||
|
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,15 +18,24 @@ package org.matrix.android.sdk.internal.database.model
|
|||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
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.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.toJSONString
|
import org.matrix.android.sdk.api.session.room.model.create.toJSONString
|
||||||
|
|
||||||
internal open class LocalRoomSummaryEntity(
|
internal open class LocalRoomSummaryEntity(
|
||||||
@PrimaryKey var roomId: String = "",
|
@PrimaryKey var roomId: String = "",
|
||||||
var roomSummaryEntity: RoomSummaryEntity? = null,
|
var roomSummaryEntity: RoomSummaryEntity? = null,
|
||||||
private var createRoomParamsStr: String? = null
|
var replacementRoomId: String? = null,
|
||||||
) : RealmObject() {
|
) : 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?
|
var createRoomParams: CreateRoomParams?
|
||||||
get() {
|
get() {
|
||||||
return CreateRoomParams.fromJson(createRoomParamsStr)
|
return CreateRoomParams.fromJson(createRoomParamsStr)
|
||||||
|
@ -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.LocalRoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
|
||||||
|
|
||||||
internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<LocalRoomSummaryEntity> {
|
internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<LocalRoomSummaryEntity> {
|
||||||
val query = realm.where<LocalRoomSummaryEntity>()
|
return realm.where<LocalRoomSummaryEntity>().equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
|
||||||
if (roomId != null) {
|
|
||||||
query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,11 @@ internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: Strin
|
|||||||
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
|
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery<ReadReceiptEntity> {
|
||||||
|
return realm.where<ReadReceiptEntity>()
|
||||||
|
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
|
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
|
||||||
return ReadReceiptEntity().apply {
|
return ReadReceiptEntity().apply {
|
||||||
this.primaryKey = "${roomId}_$userId"
|
this.primaryKey = "${roomId}_$userId"
|
||||||
|
@ -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.crypto.RoomCryptoService
|
||||||
import org.matrix.android.sdk.api.session.room.location.LocationSharingService
|
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.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.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
||||||
@ -82,6 +83,14 @@ internal class DefaultRoom(
|
|||||||
return roomSummaryDataSource.getRoomSummary(roomId)
|
return roomSummaryDataSource.getRoomSummary(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun localRoomSummary(): LocalRoomSummary? {
|
||||||
|
return roomSummaryDataSource.getLocalRoomSummary(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun asSpace(): Space? {
|
override fun asSpace(): Space? {
|
||||||
if (roomSummary()?.roomType != RoomType.SPACE) return null
|
if (roomSummary()?.roomType != RoomType.SPACE) return null
|
||||||
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
|
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
|
||||||
|
@ -29,10 +29,12 @@ 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.UpdatableLivePageResult
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
|
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.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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
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.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
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.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
@ -106,6 +108,10 @@ internal class DefaultRoomService @Inject constructor(
|
|||||||
return roomSummaryDataSource.getRoomSummaryLive(roomId)
|
return roomSummaryDataSource.getRoomSummaryLive(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getRoomSummaries(
|
override fun getRoomSummaries(
|
||||||
queryParams: RoomSummaryQueryParams,
|
queryParams: RoomSummaryQueryParams,
|
||||||
sortOrder: RoomSortOrder
|
sortOrder: RoomSortOrder
|
||||||
@ -173,8 +179,11 @@ internal class DefaultRoomService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onRoomDisplayed(roomId: String) {
|
override suspend fun onRoomDisplayed(roomId: String) {
|
||||||
|
// 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))
|
updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
|
override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
|
||||||
joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
|
joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
|
||||||
|
@ -17,38 +17,23 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room.create
|
package org.matrix.android.sdk.internal.session.room.create
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.kotlin.where
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
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.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.failure.CreateRoomFailure
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
|
||||||
|
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.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.internal.database.awaitNotEmptyResult
|
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.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
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.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.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
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.where
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
|
||||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
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 java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -56,94 +41,100 @@ import javax.inject.Inject
|
|||||||
* Create a room on the server from a local room.
|
* 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 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.
|
* 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<CreateRoomFromLocalRoomTask.Params, String> {
|
internal interface CreateRoomFromLocalRoomTask : Task<CreateRoomFromLocalRoomTask.Params, String> {
|
||||||
data class Params(val localRoomId: String)
|
data class Params(val localRoomId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
|
internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
|
||||||
@UserId private val userId: String,
|
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val createRoomTask: CreateRoomTask,
|
private val createRoomTask: CreateRoomTask,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||||
private val clock: Clock,
|
|
||||||
) : CreateRoomFromLocalRoomTask {
|
) : CreateRoomFromLocalRoomTask {
|
||||||
|
|
||||||
private val realmConfiguration
|
private val realmConfiguration
|
||||||
get() = monarchy.realmConfiguration
|
get() = monarchy.realmConfiguration
|
||||||
|
|
||||||
override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
|
override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
|
||||||
val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
|
val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId)
|
||||||
?.content.toModel<RoomTombstoneContent>()
|
?: error("## CreateRoomFromLocalRoomTask - Cannot retrieve LocalRoomSummary with roomId ${params.localRoomId}")
|
||||||
?.replacementRoomId
|
|
||||||
|
|
||||||
if (replacementRoomId != null) {
|
// If a room has already been created for the given local room, return the existing roomId
|
||||||
|
if (localRoomSummary.replacementRoomId != null) {
|
||||||
|
return localRoomSummary.replacementRoomId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localRoomSummary.createRoomParams != null && localRoomSummary.roomSummary != null) {
|
||||||
|
return createRoom(params.localRoomId, localRoomSummary.roomSummary, localRoomSummary.createRoomParams)
|
||||||
|
} else {
|
||||||
|
error("## CreateRoomFromLocalRoomTask - Invalid LocalRoomSummary: $localRoomSummary")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(createRoomParams)
|
||||||
|
}.fold(
|
||||||
|
{ it },
|
||||||
|
{
|
||||||
|
updateCreationState(localRoomId, LocalRoomCreationState.FAILURE)
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
updateReplacementRoomId(localRoomId, replacementRoomId)
|
||||||
|
waitForRoomEvents(replacementRoomId, localRoomSummary)
|
||||||
|
updateCreationState(localRoomId, LocalRoomCreationState.CREATED)
|
||||||
return replacementRoomId
|
return replacementRoomId
|
||||||
}
|
}
|
||||||
|
|
||||||
var createRoomParams: CreateRoomParams? = null
|
/**
|
||||||
var isEncrypted = false
|
* Wait for all the room events before triggering the created state.
|
||||||
monarchy.doWithRealm { realm ->
|
*
|
||||||
realm.where<LocalRoomSummaryEntity>()
|
* @param replacementRoomId the identifier of the created room
|
||||||
.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId)
|
* @param localRoomSummary the RoomSummary of the local room.
|
||||||
.findFirst()
|
*/
|
||||||
?.let {
|
private suspend fun waitForRoomEvents(replacementRoomId: String, localRoomSummary: RoomSummary) {
|
||||||
createRoomParams = it.createRoomParams
|
|
||||||
isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val roomId = createRoomTask.execute(createRoomParams!!)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for all the room events before triggering the replacement room
|
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
realm.where(RoomSummaryEntity::class.java)
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
.equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
|
||||||
.equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0)
|
.equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, localRoomSummary.invitedMembersCount)
|
||||||
}
|
}
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
EventEntity.whereRoomId(realm, roomId)
|
EventEntity.whereRoomId(realm, replacementRoomId)
|
||||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY)
|
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY)
|
||||||
}
|
}
|
||||||
if (isEncrypted) {
|
if (localRoomSummary.isEncrypted) {
|
||||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
EventEntity.whereRoomId(realm, roomId)
|
EventEntity.whereRoomId(realm, replacementRoomId)
|
||||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
|
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw CreateRoomFailure.CreatedWithTimeout(roomId)
|
updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.FAILURE)
|
||||||
|
throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createTombstoneEvent(params, roomId)
|
private fun updateCreationState(roomId: String, creationState: LocalRoomCreationState) {
|
||||||
return roomId
|
monarchy.runTransactionSync { realm ->
|
||||||
|
LocalRoomSummaryEntity.where(realm, roomId).findFirst()?.creationState = creationState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun updateReplacementRoomId(localRoomId: String, replacementRoomId: String) {
|
||||||
* Create a Tombstone event to indicate that the local room has been replaced by a new one.
|
monarchy.runTransactionSync { realm ->
|
||||||
*/
|
LocalRoomSummaryEntity.where(realm, localRoomId).findFirst()?.replacementRoomId = replacementRoomId
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.CurrentStateEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
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.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.RoomEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
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.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
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.database.query.whereRoomId
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params
|
import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params
|
||||||
@ -50,6 +53,12 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor(
|
|||||||
if (RoomLocalEcho.isLocalEchoId(roomId)) {
|
if (RoomLocalEcho.isLocalEchoId(roomId)) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
Timber.i("## DeleteLocalRoomTask - delete local room id $roomId")
|
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()
|
RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll()
|
||||||
?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") }
|
?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") }
|
||||||
?.deleteAllFromRealm()
|
?.deleteAllFromRealm()
|
||||||
|
@ -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.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
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.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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
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.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.session.space.SpaceSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
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.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.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.query.findByAlias
|
import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||||
@ -57,6 +60,7 @@ import javax.inject.Inject
|
|||||||
internal class RoomSummaryDataSource @Inject constructor(
|
internal class RoomSummaryDataSource @Inject constructor(
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val roomSummaryMapper: RoomSummaryMapper,
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
|
private val localRoomSummaryMapper: LocalRoomSummaryMapper,
|
||||||
private val queryStringValueProcessor: QueryStringValueProcessor,
|
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<Optional<LocalRoomSummary>> {
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm -> LocalRoomSummaryEntity.where(realm, roomId) },
|
||||||
|
{ localRoomSummaryMapper.map(it) }
|
||||||
|
)
|
||||||
|
return Transformations.map(liveData) { results ->
|
||||||
|
results.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getRoomSummariesLive(
|
fun getRoomSummariesLive(
|
||||||
queryParams: RoomSummaryQueryParams,
|
queryParams: RoomSummaryQueryParams,
|
||||||
sortOrder: RoomSortOrder = RoomSortOrder.NONE
|
sortOrder: RoomSortOrder = RoomSortOrder.NONE
|
||||||
|
@ -22,21 +22,22 @@ import io.mockk.coVerify
|
|||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.spyk
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import io.mockk.verifyOrder
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
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.awaitNotEmptyResult
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
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.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.model.LocalRoomSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
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.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.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 A_LOCAL_ROOM_ID = "local.a-local-room-id"
|
||||||
private const val AN_EXISTING_ROOM_ID = "an-existing-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 A_ROOM_ID = "a-room-id"
|
||||||
private const val MY_USER_ID = "my-user-id"
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
internal class DefaultCreateRoomFromLocalRoomTaskTest {
|
internal class DefaultCreateRoomFromLocalRoomTaskTest {
|
||||||
|
|
||||||
private val fakeMonarchy = FakeMonarchy()
|
private val fakeMonarchy = FakeMonarchy()
|
||||||
private val clock = DefaultClock()
|
|
||||||
private val createRoomTask = mockk<CreateRoomTask>()
|
private val createRoomTask = mockk<CreateRoomTask>()
|
||||||
private val fakeStateEventDataSource = FakeStateEventDataSource()
|
private val fakeRoomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||||
|
|
||||||
private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask(
|
private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask(
|
||||||
userId = MY_USER_ID,
|
|
||||||
monarchy = fakeMonarchy.instance,
|
monarchy = fakeMonarchy.instance,
|
||||||
createRoomTask = createRoomTask,
|
createRoomTask = createRoomTask,
|
||||||
stateEventDataSource = fakeStateEventDataSource.instance,
|
roomSummaryDataSource = fakeRoomSummaryDataSource.instance,
|
||||||
clock = clock
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -91,13 +87,12 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `given a local room id when execute then the existing room id is kept`() = runTest {
|
fun `given a local room id when execute then the existing room id is kept`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
givenATombstoneEvent(
|
val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
|
||||||
Event(
|
givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aCreationState = LocalRoomCreationState.CREATED, aReplacementRoomId = AN_EXISTING_ROOM_ID)
|
||||||
roomId = A_LOCAL_ROOM_ID,
|
val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(
|
||||||
type = EventType.STATE_ROOM_TOMBSTONE,
|
aCreateRoomParams = aCreateRoomParams,
|
||||||
stateKey = "",
|
aCreationState = LocalRoomCreationState.CREATED,
|
||||||
content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent()
|
aReplacementRoomId = AN_EXISTING_ROOM_ID
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
@ -105,20 +100,18 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
|
|||||||
val result = defaultCreateRoomFromLocalRoomTask.execute(params)
|
val result = defaultCreateRoomFromLocalRoomTask.execute(params)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
verifyTombstoneEvent(AN_EXISTING_ROOM_ID)
|
fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
|
||||||
result shouldBeEqualTo AN_EXISTING_ROOM_ID
|
result shouldBeEqualTo AN_EXISTING_ROOM_ID
|
||||||
|
aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo AN_EXISTING_ROOM_ID
|
||||||
|
aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a local room id when execute then it is correctly executed`() = runTest {
|
fun `given a local room id when execute then it is correctly executed`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aCreateRoomParams = mockk<CreateRoomParams>()
|
val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
|
||||||
val aLocalRoomSummaryEntity = mockk<LocalRoomSummaryEntity> {
|
givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
|
||||||
every { roomSummaryEntity } returns mockk(relaxed = true)
|
val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
|
||||||
every { createRoomParams } returns aCreateRoomParams
|
|
||||||
}
|
|
||||||
givenATombstoneEvent(null)
|
|
||||||
givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity)
|
|
||||||
|
|
||||||
coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID
|
coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID
|
||||||
|
|
||||||
@ -127,32 +120,84 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
|
|||||||
val result = defaultCreateRoomFromLocalRoomTask.execute(params)
|
val result = defaultCreateRoomFromLocalRoomTask.execute(params)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
verifyTombstoneEvent(null)
|
fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
|
||||||
// CreateRoomTask has been called with the initial CreateRoomParams
|
// CreateRoomTask has been called with the initial CreateRoomParams
|
||||||
coVerify { createRoomTask.execute(aCreateRoomParams) }
|
coVerify { createRoomTask.execute(aCreateRoomParams) }
|
||||||
// The resulting roomId matches the roomId returned by the createRoomTask
|
// The resulting roomId matches the roomId returned by the createRoomTask
|
||||||
result shouldBeEqualTo A_ROOM_ID
|
result shouldBeEqualTo A_ROOM_ID
|
||||||
// A tombstone state event has been created
|
// The room creation state has correctly been updated
|
||||||
coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) }
|
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?) {
|
@Test
|
||||||
fakeStateEventDataSource.givenGetStateEventReturns(event)
|
fun `given a local room id when execute with an exception then the creation state is correctly updated`() = runTest {
|
||||||
|
// Given
|
||||||
|
val aCreateRoomParams = mockk<CreateRoomParams>(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 {
|
every {
|
||||||
fakeMonarchy.fakeRealm.instance
|
fakeMonarchy.fakeRealm.instance
|
||||||
.where<LocalRoomSummaryEntity>()
|
.where<LocalRoomSummaryEntity>()
|
||||||
.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID)
|
.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
} returns localRoomSummaryEntity
|
} returns aLocalRoomSummaryEntity
|
||||||
}
|
return aLocalRoomSummaryEntity
|
||||||
|
|
||||||
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<RoomTombstoneContent>()
|
|
||||||
?.replacementRoomId shouldBeEqualTo expectedRoomId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,11 @@ internal class FakeMonarchy {
|
|||||||
} coAnswers {
|
} coAnswers {
|
||||||
firstArg<Monarchy.RealmBlock>().doWithRealm(fakeRealm.instance)
|
firstArg<Monarchy.RealmBlock>().doWithRealm(fakeRealm.instance)
|
||||||
}
|
}
|
||||||
|
coEvery {
|
||||||
|
instance.runTransactionSync(any())
|
||||||
|
} coAnswers {
|
||||||
|
firstArg<Realm.Transaction>().execute(fakeRealm.instance)
|
||||||
|
}
|
||||||
every { instance.realmConfiguration } returns mockk()
|
every { instance.realmConfiguration } returns mockk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.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.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) }
|
||||||
|
}
|
||||||
|
}
|
@ -372,7 +372,7 @@ dependencies {
|
|||||||
|
|
||||||
gplayImplementation "com.google.android.gms:play-services-location:20.0.0"
|
gplayImplementation "com.google.android.gms:play-services-location:20.0.0"
|
||||||
// UnifiedPush gplay flavor only
|
// 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-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
@ -225,8 +225,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
|
|||||||
|
|
||||||
// Wait until local secrets are known (gossip)
|
// Wait until local secrets are known (gossip)
|
||||||
withIdlingResource(allSecretsKnownIdling(uiSession)) {
|
withIdlingResource(allSecretsKnownIdling(uiSession)) {
|
||||||
onView(withId(R.id.groupToolbarAvatarImageView))
|
onView(withId(R.id.roomListContainer))
|
||||||
.perform(click())
|
.check(matches(isDisplayed()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ import im.vector.app.withIdlingResource
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class ElementRobot(
|
class ElementRobot(
|
||||||
private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(false)
|
private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(true)
|
||||||
) {
|
) {
|
||||||
fun onboarding(block: OnboardingRobot.() -> Unit) {
|
fun onboarding(block: OnboardingRobot.() -> Unit) {
|
||||||
block(OnboardingRobot())
|
block(OnboardingRobot())
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<!-- Level 1: Labs -->
|
<!-- Level 1: Labs -->
|
||||||
<bool name="settings_labs_thread_messages_default">false</bool>
|
<bool name="settings_labs_thread_messages_default">false</bool>
|
||||||
<bool name="settings_labs_new_app_layout_default">false</bool>
|
<bool name="settings_labs_new_app_layout_default">true</bool>
|
||||||
<bool name="settings_timeline_show_live_sender_info_visible">true</bool>
|
<bool name="settings_timeline_show_live_sender_info_visible">true</bool>
|
||||||
<bool name="settings_timeline_show_live_sender_info_default">false</bool>
|
<bool name="settings_timeline_show_live_sender_info_default">false</bool>
|
||||||
<!-- Level 1: Advanced settings -->
|
<!-- Level 1: Advanced settings -->
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.invite.SpaceInviteBottomSheet
|
||||||
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import im.vector.app.features.usercode.UserCodeActivity
|
||||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -634,10 +635,18 @@ class HomeActivity :
|
|||||||
launchInviteFriends()
|
launchInviteFriends()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_home_qr -> {
|
||||||
|
launchQrCode()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchQrCode() {
|
||||||
|
startActivity(UserCodeActivity.newIntent(this, sharedActionViewModel.session.myUserId))
|
||||||
|
}
|
||||||
|
|
||||||
private fun launchInviteFriends() {
|
private fun launchInviteFriends() {
|
||||||
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
|
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
|
||||||
analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
|
analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
|
||||||
|
@ -119,8 +119,9 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeReleaseNotes() = withState { state ->
|
private fun observeReleaseNotes() = withState { state ->
|
||||||
|
if (vectorPreferences.isNewAppLayoutEnabled()) {
|
||||||
// we don't want to show release notes for new users or after relogin
|
// we don't want to show release notes for new users or after relogin
|
||||||
if (state.authenticationDescription == null && vectorPreferences.isNewAppLayoutEnabled()) {
|
if (state.authenticationDescription == null) {
|
||||||
releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown ->
|
releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown ->
|
||||||
if (!isAppLayoutOnboardingShown) {
|
if (!isAppLayoutOnboardingShown) {
|
||||||
_viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes)
|
_viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes)
|
||||||
@ -133,6 +134,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeAnalytics() {
|
private fun observeAnalytics() {
|
||||||
if (analyticsConfig.isEnabled) {
|
if (analyticsConfig.isEnabled) {
|
||||||
|
@ -51,7 +51,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||||||
object OpenRoomProfile : RoomDetailViewEvents()
|
object OpenRoomProfile : RoomDetailViewEvents()
|
||||||
data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : 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()
|
object HideWaitingView : RoomDetailViewEvents()
|
||||||
|
|
||||||
data class DownloadFileState(
|
data class DownloadFileState(
|
||||||
|
@ -493,7 +493,7 @@ class TimelineFragment :
|
|||||||
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
|
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
|
||||||
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
|
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
|
||||||
RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference()
|
RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference()
|
||||||
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
|
is RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView(it.text)
|
||||||
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
||||||
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
||||||
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
|
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
|
||||||
|
@ -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.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
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.location.UpdateLiveLocationShareResult
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
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.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.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
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.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.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
|
import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
|
||||||
@ -185,6 +186,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
init {
|
init {
|
||||||
// This method will take care of a null room to update the state.
|
// This method will take care of a null room to update the state.
|
||||||
observeRoomSummary()
|
observeRoomSummary()
|
||||||
|
observeLocalRoomSummary()
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
timeline = null
|
timeline = null
|
||||||
} else {
|
} else {
|
||||||
@ -617,7 +619,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
|
private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView())
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val widget = jitsiService.createJitsiWidget(initialState.roomId, action.withVideo)
|
val widget = jitsiService.createJitsiWidget(initialState.roomId, action.withVideo)
|
||||||
@ -637,7 +639,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
if (isJitsiWidget) {
|
if (isJitsiWidget) {
|
||||||
setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) }
|
setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) }
|
||||||
} else {
|
} else {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView())
|
||||||
}
|
}
|
||||||
session.widgetService().destroyRoomWidget(initialState.roomId, widgetId)
|
session.widgetService().destroyRoomWidget(initialState.roomId, widgetId)
|
||||||
// local echo
|
// 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() {
|
private fun getUnreadState() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
combine(
|
combine(
|
||||||
@ -1322,26 +1346,11 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also {
|
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<RoomTombstoneContent>()
|
|
||||||
?.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
|
* 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
|
* in the snapshot. The main reason for this function is to support the /relations api
|
||||||
|
@ -31,4 +31,5 @@ sealed class RoomListAction : VectorViewModelAction {
|
|||||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||||
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
||||||
data class ShowRoomDetails(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
data class ShowRoomDetails(val roomId: String, val viaServers: List<String>?) : RoomListAction()
|
||||||
|
object DeleteAllLocalRoom : RoomListAction()
|
||||||
}
|
}
|
||||||
|
@ -149,10 +149,13 @@ class RoomListFragment :
|
|||||||
(it.contentEpoxyController as? RoomSummaryPagedController)?.roomChangeMembershipStates = ms
|
(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() {
|
private fun refreshCollapseStates() {
|
||||||
|
@ -97,7 +97,6 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
observeMembershipChanges()
|
observeMembershipChanges()
|
||||||
observeLocalRooms()
|
|
||||||
|
|
||||||
spaceStateHandler.getSelectedSpaceFlow()
|
spaceStateHandler.getSelectedSpaceFlow()
|
||||||
.distinctUntilChanged()
|
.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<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
private val roomListSectionBuilder = RoomListSectionBuilder(
|
private val roomListSectionBuilder = RoomListSectionBuilder(
|
||||||
@ -166,6 +155,7 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
||||||
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
|
is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
|
||||||
is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
|
is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
|
||||||
|
RoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,14 +163,6 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
|
return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteLocalRooms(roomsIds: Iterable<String>) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
roomsIds.forEach {
|
|
||||||
session.roomService().deleteLocalRoom(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {
|
private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {
|
||||||
@ -338,4 +320,16 @@ class RoomListViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(value)
|
_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ data class RoomListViewState(
|
|||||||
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
|
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
|
||||||
val currentUserName: String? = null,
|
val currentUserName: String? = null,
|
||||||
val asyncSelectedSpace: Async<RoomSummary?> = Uninitialized,
|
val asyncSelectedSpace: Async<RoomSummary?> = Uninitialized,
|
||||||
val localRoomIds: List<String> = emptyList()
|
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
||||||
|
@ -103,6 +103,9 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var showSelected: Boolean = false
|
var showSelected: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var useSingleLineForLastEvent: Boolean = false
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
|
||||||
@ -122,6 +125,10 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
|
|||||||
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
||||||
renderSelection(holder, showSelected)
|
renderSelection(holder, showSelected)
|
||||||
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
|
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
|
||||||
|
|
||||||
|
if (useSingleLineForLastEvent) {
|
||||||
|
holder.subtitleView.setLines(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
|
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
|
||||||
|
@ -51,7 +51,8 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||||
selectedRoomIds: Set<String>,
|
selectedRoomIds: Set<String>,
|
||||||
displayMode: RoomListDisplayMode,
|
displayMode: RoomListDisplayMode,
|
||||||
listener: RoomListListener?
|
listener: RoomListListener?,
|
||||||
|
singleLineLastEvent: Boolean = false
|
||||||
): VectorEpoxyModel<*> {
|
): VectorEpoxyModel<*> {
|
||||||
return when (roomSummary.membership) {
|
return when (roomSummary.membership) {
|
||||||
Membership.INVITE -> {
|
Membership.INVITE -> {
|
||||||
@ -59,7 +60,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||||||
createInvitationItem(roomSummary, changeMembershipState, listener)
|
createInvitationItem(roomSummary, changeMembershipState, listener)
|
||||||
}
|
}
|
||||||
else -> createRoomItem(
|
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,
|
roomSummary: RoomSummary,
|
||||||
selectedRoomIds: Set<String>,
|
selectedRoomIds: Set<String>,
|
||||||
displayMode: RoomListDisplayMode,
|
displayMode: RoomListDisplayMode,
|
||||||
|
singleLineLastEvent: Boolean,
|
||||||
onClick: ((RoomSummary) -> Unit)?,
|
onClick: ((RoomSummary) -> Unit)?,
|
||||||
onLongClick: ((RoomSummary) -> Boolean)?
|
onLongClick: ((RoomSummary) -> Boolean)?,
|
||||||
): VectorEpoxyModel<*> {
|
): VectorEpoxyModel<*> {
|
||||||
val subtitle = getSearchResultSubtitle(roomSummary)
|
val subtitle = getSearchResultSubtitle(roomSummary)
|
||||||
val unreadCount = roomSummary.notificationCount
|
val unreadCount = roomSummary.notificationCount
|
||||||
@ -140,7 +142,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
createRoomSummaryItem(
|
createRoomSummaryItem(
|
||||||
roomSummary, displayMode, subtitle, latestEventTime, typingMessage,
|
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,
|
showHighlighted: Boolean,
|
||||||
showSelected: Boolean,
|
showSelected: Boolean,
|
||||||
unreadCount: Int,
|
unreadCount: Int,
|
||||||
|
singleLineLastEvent: Boolean,
|
||||||
onClick: ((RoomSummary) -> Unit)?,
|
onClick: ((RoomSummary) -> Unit)?,
|
||||||
onLongClick: ((RoomSummary) -> Boolean)?
|
onLongClick: ((RoomSummary) -> Boolean)?
|
||||||
) = RoomSummaryItem_()
|
) = RoomSummaryItem_()
|
||||||
@ -177,6 +180,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||||||
.unreadNotificationCount(unreadCount)
|
.unreadNotificationCount(unreadCount)
|
||||||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||||
|
.useSingleLineForLastEvent(singleLineLastEvent)
|
||||||
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
||||||
.itemClickListener { onClick?.invoke(roomSummary) }
|
.itemClickListener { onClick?.invoke(roomSummary) }
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
@ -23,5 +25,18 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
|
|||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(R.layout.item_room_placeholder) {
|
abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(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<TextView>(R.id.subtitleView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,26 @@
|
|||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
class RoomSummaryListController(
|
class RoomSummaryListController(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
private val displayMode: RoomListDisplayMode
|
private val displayMode: RoomListDisplayMode,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
|
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
|
||||||
|
|
||||||
var listener: RoomListListener? = null
|
var listener: RoomListListener? = null
|
||||||
|
private val shouldUseSingleLine: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||||
|
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildModels(data: List<RoomSummary>?) {
|
override fun buildModels(data: List<RoomSummary>?) {
|
||||||
data?.forEach {
|
data?.forEach {
|
||||||
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener))
|
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener, shouldUseSingleLine))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,18 +20,26 @@ import com.airbnb.epoxy.EpoxyModel
|
|||||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
import im.vector.app.core.utils.createUIHandler
|
import im.vector.app.core.utils.createUIHandler
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
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.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
class RoomSummaryPagedController(
|
class RoomSummaryPagedController(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
private val displayMode: RoomListDisplayMode
|
private val displayMode: RoomListDisplayMode,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : PagedListEpoxyController<RoomSummary>(
|
) : PagedListEpoxyController<RoomSummary>(
|
||||||
// Important it must match the PageList builder notify Looper
|
// Important it must match the PageList builder notify Looper
|
||||||
modelBuildingHandler = createUIHandler()
|
modelBuildingHandler = createUIHandler()
|
||||||
), CollapsableControllerExtension {
|
), CollapsableControllerExtension {
|
||||||
|
|
||||||
var listener: RoomListListener? = null
|
var listener: RoomListListener? = null
|
||||||
|
private val shouldUseSingleLine: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||||
|
shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -57,8 +65,14 @@ class RoomSummaryPagedController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||||
// for place holder if enabled
|
return if (item == null) {
|
||||||
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
|
val host = this
|
||||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener)
|
RoomSummaryItemPlaceHolder_().apply {
|
||||||
|
id(currentPosition)
|
||||||
|
useSingleLineForLastEvent(host.shouldUseSingleLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener, shouldUseSingleLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,18 +17,20 @@
|
|||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
|
import im.vector.app.features.settings.FontScalePreferences
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryPagedControllerFactory @Inject constructor(
|
class RoomSummaryPagedControllerFactory @Inject constructor(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
|
private val fontScalePreferences: FontScalePreferences
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
|
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
|
||||||
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
|
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode, fontScalePreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
|
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
|
||||||
return RoomSummaryListController(roomSummaryItemFactory, displayMode)
|
return RoomSummaryListController(roomSummaryItemFactory, displayMode, fontScalePreferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSuggestedRoomListController(): SuggestedRoomListController {
|
fun createSuggestedRoomListController(): SuggestedRoomListController {
|
||||||
|
@ -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.RoomListListener
|
||||||
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
||||||
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
|
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.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HomeFilteredRoomsController @Inject constructor(
|
class HomeFilteredRoomsController @Inject constructor(
|
||||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||||
|
fontScalePreferences: FontScalePreferences
|
||||||
) : PagedListEpoxyController<RoomSummary>(
|
) : PagedListEpoxyController<RoomSummary>(
|
||||||
// Important it must match the PageList builder notify Looper
|
// Important it must match the PageList builder notify Looper
|
||||||
modelBuildingHandler = createUIHandler()
|
modelBuildingHandler = createUIHandler()
|
||||||
@ -47,6 +49,13 @@ class HomeFilteredRoomsController @Inject constructor(
|
|||||||
private var emptyStateData: StateView.State.Empty? = null
|
private var emptyStateData: StateView.State.Empty? = null
|
||||||
private var currentState: StateView.State = StateView.State.Content
|
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<EpoxyModel<*>>) {
|
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||||
if (models.isEmpty() && emptyStateData != null) {
|
if (models.isEmpty() && emptyStateData != null) {
|
||||||
emptyStateData?.let { emptyState ->
|
emptyStateData?.let { emptyState ->
|
||||||
@ -67,7 +76,14 @@ class HomeFilteredRoomsController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||||
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
|
return if (item == null) {
|
||||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
|
val host = this
|
||||||
|
RoomSummaryItemPlaceHolder_().apply {
|
||||||
|
id(currentPosition)
|
||||||
|
useSingleLineForLastEvent(host.shouldUseSingleLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener, shouldUseSingleLine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,5 @@ sealed class HomeRoomListAction : VectorViewModelAction {
|
|||||||
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
|
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
|
||||||
data class LeaveRoom(val roomId: String) : HomeRoomListAction()
|
data class LeaveRoom(val roomId: String) : HomeRoomListAction()
|
||||||
data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
|
data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
|
||||||
|
object DeleteAllLocalRoom : HomeRoomListAction()
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import android.view.ViewGroup
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
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.StateView
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
|
import im.vector.app.core.utils.FirstItemUpdatedObserver
|
||||||
import im.vector.app.databinding.FragmentRoomListBinding
|
import im.vector.app.databinding.FragmentRoomListBinding
|
||||||
import im.vector.app.features.analytics.plan.ViewRoom
|
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.RoomListAnimator
|
||||||
@ -66,6 +66,7 @@ class HomeRoomListFragment :
|
|||||||
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
|
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
|
||||||
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
|
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
|
||||||
private var concatAdapter = ConcatAdapter()
|
private var concatAdapter = ConcatAdapter()
|
||||||
|
private lateinit var firstItemObserver: FirstItemUpdatedObserver
|
||||||
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
|
|
||||||
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
||||||
@ -82,6 +83,13 @@ class HomeRoomListFragment :
|
|||||||
setupRecyclerView()
|
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() {
|
private fun setupObservers() {
|
||||||
sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
|
sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
|
||||||
sharedQuickActionsViewModel
|
sharedQuickActionsViewModel
|
||||||
@ -130,6 +138,9 @@ class HomeRoomListFragment :
|
|||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
val layoutManager = LinearLayoutManager(context)
|
val layoutManager = LinearLayoutManager(context)
|
||||||
|
firstItemObserver = FirstItemUpdatedObserver(layoutManager) {
|
||||||
|
layoutManager.scrollToPosition(0)
|
||||||
|
}
|
||||||
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
views.roomListView.layoutManager = layoutManager
|
views.roomListView.layoutManager = layoutManager
|
||||||
views.roomListView.itemAnimator = RoomListAnimator()
|
views.roomListView.itemAnimator = RoomListAnimator()
|
||||||
@ -158,14 +169,7 @@ class HomeRoomListFragment :
|
|||||||
|
|
||||||
views.roomListView.adapter = concatAdapter
|
views.roomListView.adapter = concatAdapter
|
||||||
|
|
||||||
// we need to force scroll when recents/filter tabs are added to make them visible
|
concatAdapter.registerAdapterDataObserver(firstItemObserver)
|
||||||
concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
|
||||||
if (positionStart == 0) {
|
|
||||||
layoutManager.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||||
@ -233,6 +237,8 @@ class HomeRoomListFragment :
|
|||||||
|
|
||||||
roomsController.listener = null
|
roomsController.listener = null
|
||||||
|
|
||||||
|
concatAdapter.unregisterAdapterDataObserver(firstItemObserver)
|
||||||
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
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.RoomCategoryFilter
|
||||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||||
import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
|
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.UpdatableLivePageResult
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
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.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.model.tag.RoomTag
|
||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.state.isPublic
|
import org.matrix.android.sdk.api.session.room.state.isPublic
|
||||||
@ -329,6 +331,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||||||
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||||
is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
|
is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
|
||||||
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action.filter)
|
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action.filter)
|
||||||
|
HomeRoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,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? {
|
private fun String.otherTag(): String? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY
|
RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY
|
||||||
|
@ -26,5 +26,5 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|||||||
data class HomeRoomListViewState(
|
data class HomeRoomListViewState(
|
||||||
val state: StateView.State = StateView.State.Content,
|
val state: StateView.State = StateView.State.Content,
|
||||||
val headersData: RoomsHeadersData = RoomsHeadersData(),
|
val headersData: RoomsHeadersData = RoomsHeadersData(),
|
||||||
val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null
|
val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null,
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
@ -18,7 +18,7 @@ package im.vector.app.features.home.room.list.home.header
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.airbnb.epoxy.Carousel
|
import com.airbnb.epoxy.Carousel
|
||||||
import com.airbnb.epoxy.CarouselModelBuilder
|
import com.airbnb.epoxy.CarouselModelBuilder
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
@ -27,6 +27,7 @@ import com.airbnb.epoxy.carousel
|
|||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
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.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.list.RoomListListener
|
import im.vector.app.features.home.room.list.RoomListListener
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
@ -47,22 +48,7 @@ class HomeRoomsHeadersController @Inject constructor(
|
|||||||
|
|
||||||
private var carousel: Carousel? = null
|
private var carousel: Carousel? = null
|
||||||
|
|
||||||
private val carouselAdapterObserver = object : RecyclerView.AdapterDataObserver() {
|
private var carouselAdapterObserver: FirstItemUpdatedObserver? = null
|
||||||
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 val recentsHPadding = TypedValue.applyDimension(
|
private val recentsHPadding = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
@ -113,25 +99,16 @@ class HomeRoomsHeadersController @Inject constructor(
|
|||||||
)
|
)
|
||||||
onBind { _, view, _ ->
|
onBind { _, view, _ ->
|
||||||
host.carousel = view
|
host.carousel = view
|
||||||
|
host.unsubscribeAdapterObserver()
|
||||||
|
host.subscribeAdapterObserver()
|
||||||
|
|
||||||
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
|
||||||
view.setBackgroundColor(colorSurface)
|
view.setBackgroundColor(colorSurface)
|
||||||
|
|
||||||
try {
|
|
||||||
view.adapter?.registerAdapterDataObserver(host.carouselAdapterObserver)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnbind { _, view ->
|
onUnbind { _, _ ->
|
||||||
host.carousel = null
|
host.carousel = null
|
||||||
|
host.unsubscribeAdapterObserver()
|
||||||
try {
|
|
||||||
view.adapter?.unregisterAdapterDataObserver(host.carouselAdapterObserver)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withModelsFrom(recents) { roomSummary ->
|
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(
|
private fun addRoomFilterHeaderItem(
|
||||||
filterChangedListener: ((HomeRoomFilter) -> Unit)?,
|
filterChangedListener: ((HomeRoomFilter) -> Unit)?,
|
||||||
filtersList: List<HomeRoomFilter>,
|
filtersList: List<HomeRoomFilter>,
|
||||||
|
@ -34,7 +34,7 @@ class ReleaseNotesPreferencesStore @Inject constructor(
|
|||||||
private val context: Context
|
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<Boolean> = context.dataStore.data
|
val appLayoutOnboardingShown: Flow<Boolean> = context.dataStore.data
|
||||||
.map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() }
|
.map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() }
|
||||||
|
@ -57,6 +57,16 @@ interface FontScalePreferences {
|
|||||||
* @return list of values
|
* @return list of values
|
||||||
*/
|
*/
|
||||||
fun getAvailableScales(): List<FontScaleValue>
|
fun getAvailableScales(): List<FontScaleValue>
|
||||||
|
|
||||||
|
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(
|
private val fontScaleValues = listOf(
|
||||||
FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny),
|
FontScaleValue(0, "FONT_SCALE_TINY", FontScalePreferences.SCALE_TINY, R.string.tiny),
|
||||||
FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small),
|
FontScaleValue(1, "FONT_SCALE_SMALL", FontScalePreferences.SCALE_SMALL, R.string.small),
|
||||||
FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal),
|
FontScaleValue(2, "FONT_SCALE_NORMAL", FontScalePreferences.SCALE_NORMAL, R.string.normal),
|
||||||
FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large),
|
FontScaleValue(3, "FONT_SCALE_LARGE", FontScalePreferences.SCALE_LARGE, R.string.large),
|
||||||
FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger),
|
FontScaleValue(4, "FONT_SCALE_LARGER", FontScalePreferences.SCALE_LARGER, R.string.larger),
|
||||||
FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest),
|
FontScaleValue(5, "FONT_SCALE_LARGEST", FontScalePreferences.SCALE_LARGEST, R.string.largest),
|
||||||
FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge)
|
FontScaleValue(6, "FONT_SCALE_HUGE", FontScalePreferences.SCALE_HUGE, R.string.huge)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val normalFontScaleValue = fontScaleValues[2]
|
private val normalFontScaleValue = fontScaleValues[2]
|
||||||
|
@ -60,6 +60,7 @@ class IncomingShareController @Inject constructor(
|
|||||||
roomSummary,
|
roomSummary,
|
||||||
data.selectedRoomIds,
|
data.selectedRoomIds,
|
||||||
RoomListDisplayMode.FILTERED,
|
RoomListDisplayMode.FILTERED,
|
||||||
|
singleLineLastEvent = false,
|
||||||
callback?.let { it::onRoomClicked },
|
callback?.let { it::onRoomClicked },
|
||||||
callback?.let { it::onRoomLongClicked }
|
callback?.let { it::onRoomLongClicked }
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
<size android:width="40dp" android:height="40dp"/>
|
|
||||||
|
|
||||||
<solid android:color="?vctr_reaction_background_off" />
|
<solid android:color="?vctr_reaction_background_off" />
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
android:id="@+id/recentRoot"
|
android:id="@+id/recentRoot"
|
||||||
android:layout_width="84dp"
|
android:layout_width="84dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?vctr_toolbar_background"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:lines="2"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?vctr_content_secondary"
|
android:textColor="?vctr_content_secondary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_marginTop="12dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
@ -29,23 +29,20 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- Margin bottom does not work, so I use space -->
|
<TextView
|
||||||
<Space
|
|
||||||
android:id="@+id/roomAvatarBottomSpace"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="12dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
|
|
||||||
tools:layout_marginStart="20dp" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/roomNameView"
|
android:id="@+id/roomNameView"
|
||||||
android:layout_width="wrap_content"
|
style="@style/Widget.Vector.TextView.Subtitle"
|
||||||
android:layout_height="15dp"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginEnd="70dp"
|
android:layout_marginEnd="70dp"
|
||||||
android:background="@drawable/placeholder_shape_8"
|
android:background="@drawable/placeholder_shape_8"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
@ -53,23 +50,38 @@
|
|||||||
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
|
app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<View
|
<TextView
|
||||||
android:id="@+id/roomTypingView"
|
android:id="@+id/subtitleView"
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="3dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:background="@drawable/placeholder_shape_8"
|
android:background="@drawable/placeholder_shape_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="2"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?vctr_content_secondary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/roomNameView"
|
app:layout_constraintStart_toStartOf="@id/roomNameView"
|
||||||
app:layout_constraintTop_toBottomOf="@id/roomNameView" />
|
app:layout_constraintTop_toBottomOf="@id/roomNameView" />
|
||||||
|
|
||||||
|
<!-- Margin bottom does not work, so I use space -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/roomAvatarBottomSpace"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="7dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/subtitleView"
|
||||||
|
tools:layout_marginStart="120dp" />
|
||||||
|
|
||||||
<!-- We use vctr_list_separator_system here for a better rendering -->
|
<!-- We use vctr_list_separator_system here for a better rendering -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/roomDividerView"
|
android:id="@+id/roomDividerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="?vctr_list_separator_system"
|
android:background="?vctr_list_separator_system"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomAvatarBottomSpace"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
android:title="@string/invite_friends"
|
android:title="@string/invite_friends"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_home_qr"
|
||||||
|
android:title="@string/add_by_qr_code"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_home_suggestion"
|
android:id="@+id/menu_home_suggestion"
|
||||||
android:icon="@drawable/ic_material_bug_report"
|
android:icon="@drawable/ic_material_bug_report"
|
||||||
@ -42,5 +47,4 @@
|
|||||||
android:title="@string/home_filter_placeholder_home"
|
android:title="@string/home_filter_placeholder_home"
|
||||||
app:iconTint="?vctr_content_secondary"
|
app:iconTint="?vctr_content_secondary"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user