mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-18 04:50:08 +03:00
Merge branch 'spaces' into sc
Change-Id: Ia2a25202fab4e5be3c4800bb5cb6ab47fc828bac
This commit is contained in:
commit
2269e0f1ed
462 changed files with 21809 additions and 2008 deletions
44
CHANGES.md
44
CHANGES.md
|
@ -1,3 +1,47 @@
|
||||||
|
Changes in Element 1.1.7 (2021-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Spaces beta
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Add ability to install APK from directly from Element (#2381)
|
||||||
|
- Delete and react to stickers (#3250)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Message states cosmetic changes (#3007)
|
||||||
|
- Fix exception in rxSingle (#3180)
|
||||||
|
- Do not invite the current user when creating a room (#3123)
|
||||||
|
- Fix color issues when the system theme is changed (#2738)
|
||||||
|
- Fix issues on Android 11 (#3067)
|
||||||
|
- Fix issue when opening encrypted files (#3186)
|
||||||
|
- Fix wording issue (#3242)
|
||||||
|
- Fix missing sender information after edits (#3184)
|
||||||
|
|
||||||
|
Translations 🗣:
|
||||||
|
-
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205)
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Upgrade to gradle 7
|
||||||
|
|
||||||
|
Test:
|
||||||
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- New store descriptions
|
||||||
|
- `master` branch has been renamed to `main`. To apply change to your dev environment, run:
|
||||||
|
```sh
|
||||||
|
git branch -m master main
|
||||||
|
git fetch origin
|
||||||
|
git branch -u origin/main main
|
||||||
|
# And optionally
|
||||||
|
git remote prune origin
|
||||||
|
```
|
||||||
|
- Allow cleartext (non-SSL) connections to Matrix servers on LAN hosts (#3166)
|
||||||
|
|
||||||
Changes in Element 1.1.6 (2021-04-16)
|
Changes in Element 1.1.6 (2021-04-16)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.3.2'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
|
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
}
|
}
|
|
@ -16,8 +16,8 @@ buildscript {
|
||||||
classpath 'com.google.gms:google-services:4.3.5'
|
classpath 'com.google.gms:google-services:4.3.5'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
|
||||||
classpath "com.likethesalad.android:string-reference:1.2.1"
|
classpath "com.likethesalad.android:string-reference:1.2.2"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -51,7 +51,7 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
maven {
|
||||||
url "http://dl.bintray.com/piasy/maven"
|
url "https://dl.bintray.com/piasy/maven"
|
||||||
content {
|
content {
|
||||||
includeGroupByRegex "com\\.github\\.piasy"
|
includeGroupByRegex "com\\.github\\.piasy"
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ allprojects {
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
// Jitsi repo
|
// Jitsi repo
|
||||||
maven {
|
maven {
|
||||||
url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
|
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.1.0"
|
||||||
// Note: to test Jitsi release you can use a local file like this:
|
// Note: to test Jitsi release you can use a local file like this:
|
||||||
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
|
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
|
||||||
}
|
}
|
||||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
|
distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest package="org.matrix.android.sdk.rx" />
|
||||||
package="org.matrix.android.sdk.rx" />
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* 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.rx
|
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import io.reactivex.Completable
|
|
||||||
import io.reactivex.Single
|
|
||||||
|
|
||||||
fun <T> singleBuilder(builder: (MatrixCallback<T>) -> Cancelable): Single<T> = Single.create { emitter ->
|
|
||||||
val callback = object : MatrixCallback<T> {
|
|
||||||
override fun onSuccess(data: T) {
|
|
||||||
// Add `!!` to fix the warning:
|
|
||||||
// "Type mismatch: type parameter with nullable bounds is used T is used where T was expected. This warning will become an error soon"
|
|
||||||
emitter.onSuccess(data!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
emitter.tryOnError(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val cancelable = builder(callback)
|
|
||||||
emitter.setCancellable {
|
|
||||||
cancelable.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> completableBuilder(builder: (MatrixCallback<T>) -> Cancelable): Completable = Completable.create { emitter ->
|
|
||||||
val callback = object : MatrixCallback<T> {
|
|
||||||
override fun onSuccess(data: T) {
|
|
||||||
emitter.onComplete()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
emitter.tryOnError(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val cancelable = builder(callback)
|
|
||||||
emitter.setCancellable {
|
|
||||||
cancelable.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
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.space.SpaceSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
|
@ -66,6 +67,13 @@ class RxSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
|
return session.spaceService().getSpaceSummariesLive(queryParams).asObservable()
|
||||||
|
.startWithCallable {
|
||||||
|
session.spaceService().getSpaceSummaries(queryParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
.startWithCallable {
|
||||||
|
@ -124,8 +132,8 @@ class RxSession(private val session: Session) {
|
||||||
.startWithCallable { session.getPendingThreePids() }
|
.startWithCallable { session.getPendingThreePids() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = rxSingle {
|
||||||
session.createRoom(roomParams, it)
|
session.createRoom(roomParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchUsersDirectory(search: String,
|
fun searchUsersDirectory(search: String,
|
||||||
|
@ -136,13 +144,13 @@ class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun joinRoom(roomIdOrAlias: String,
|
fun joinRoom(roomIdOrAlias: String,
|
||||||
reason: String? = null,
|
reason: String? = null,
|
||||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
viaServers: List<String> = emptyList()): Single<Unit> = rxSingle {
|
||||||
session.joinRoom(roomIdOrAlias, reason, viaServers, it)
|
session.joinRoom(roomIdOrAlias, reason, viaServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = singleBuilder {
|
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = rxSingle {
|
||||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
session.getRoomIdByAlias(roomAlias, searchOnServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle {
|
fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle {
|
||||||
|
|
|
@ -115,7 +115,7 @@ dependencies {
|
||||||
def lifecycle_version = '2.2.0'
|
def lifecycle_version = '2.2.0'
|
||||||
def arch_version = '2.1.0'
|
def arch_version = '2.1.0'
|
||||||
def markwon_version = '3.1.0'
|
def markwon_version = '3.1.0'
|
||||||
def daggerVersion = '2.33'
|
def daggerVersion = '2.35'
|
||||||
def work_version = '2.5.0'
|
def work_version = '2.5.0'
|
||||||
def retrofit_version = '2.9.0'
|
def retrofit_version = '2.9.0'
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ dependencies {
|
||||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
|
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.22'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||||
|
|
|
@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||||
|
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.runBlockingTest {
|
||||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encryptedRoom) {
|
if (encryptedRoom) {
|
||||||
|
@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { bobSession.joinRoom(aliceRoomId, callback = it) }
|
mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
|
||||||
|
|
||||||
mTestHelper.await(lock)
|
mTestHelper.await(lock)
|
||||||
|
|
||||||
|
@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
room.invite(samSession.myUserId, null)
|
room.invite(samSession.myUserId, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.runBlockingTest {
|
||||||
samSession.joinRoom(room.roomId, null, emptyList(), it)
|
samSession.joinRoom(room.roomId, null, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
return samSession
|
return samSession
|
||||||
|
@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createDM(alice: Session, bob: Session): String {
|
fun createDM(alice: Session, bob: Session): String {
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.runBlockingTest {
|
||||||
alice.createDirectRoom(bob.myUserId, it)
|
alice.createDirectRoom(bob.myUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.waitWithLatch { latch ->
|
mTestHelper.waitWithLatch { latch ->
|
||||||
|
@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { bob.joinRoom(roomId, callback = it) }
|
mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return roomId
|
return roomId
|
||||||
|
@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
|
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.runBlockingTest {
|
||||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||||
}
|
}
|
||||||
val room = aliceSession.getRoom(roomId)!!
|
val room = aliceSession.getRoom(roomId)!!
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
||||||
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
|
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
|
||||||
println("TEST -> " + session.myUserId + " invited")
|
println("TEST -> " + session.myUserId + " invited")
|
||||||
mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) }
|
mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
|
||||||
println("TEST -> " + session.myUserId + " joined")
|
println("TEST -> " + session.myUserId + " joined")
|
||||||
sessions.add(session)
|
sessions.add(session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,13 +71,12 @@ class KeyShareTests : InstrumentedTest {
|
||||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
// Create an encrypted room and add a message
|
// Create an encrypted room and add a message
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.runBlockingTest {
|
||||||
aliceSession.createRoom(
|
aliceSession.createRoom(
|
||||||
CreateRoomParams().apply {
|
CreateRoomParams().apply {
|
||||||
visibility = RoomDirectoryVisibility.PRIVATE
|
visibility = RoomDirectoryVisibility.PRIVATE
|
||||||
enableEncryption()
|
enableEncryption()
|
||||||
},
|
}
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val room = aliceSession.getRoom(roomId)
|
val room = aliceSession.getRoom(roomId)
|
||||||
|
@ -332,13 +331,12 @@ class KeyShareTests : InstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an encrypted room and send a couple of messages
|
// Create an encrypted room and send a couple of messages
|
||||||
val roomId = mTestHelper.doSync<String> {
|
val roomId = mTestHelper.runBlockingTest {
|
||||||
aliceSession.createRoom(
|
aliceSession.createRoom(
|
||||||
CreateRoomParams().apply {
|
CreateRoomParams().apply {
|
||||||
visibility = RoomDirectoryVisibility.PRIVATE
|
visibility = RoomDirectoryVisibility.PRIVATE
|
||||||
enableEncryption()
|
enableEncryption()
|
||||||
},
|
}
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val roomAlicePov = aliceSession.getRoom(roomId)
|
val roomAlicePov = aliceSession.getRoom(roomId)
|
||||||
|
@ -371,8 +369,8 @@ class KeyShareTests : InstrumentedTest {
|
||||||
roomAlicePov.invite(bobSession.myUserId, null)
|
roomAlicePov.invite(bobSession.myUserId, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.runBlockingTest {
|
||||||
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
|
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to discard alice outbound session
|
// we want to discard alice outbound session
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.session.space
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SpaceCreationTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val commonTestHelper = CommonTestHelper(context())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createSimplePublicSpace() {
|
||||||
|
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
|
||||||
|
val roomName = "My Space"
|
||||||
|
val topic = "A public space for test"
|
||||||
|
val spaceId: String
|
||||||
|
runBlocking {
|
||||||
|
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
|
||||||
|
// wait a bit to let the summary update it self :/
|
||||||
|
delay(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||||
|
assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||||
|
assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||||
|
// assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
|
||||||
|
|
||||||
|
assertNotNull(syncedSpace, "Space should be found by Id")
|
||||||
|
val creationEvent = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
|
||||||
|
val createContent = creationEvent?.content.toModel<RoomCreateContent>()
|
||||||
|
assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space")
|
||||||
|
|
||||||
|
var powerLevelsContent: PowerLevelsContent? = null
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
|
val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel<PowerLevelsContent>()
|
||||||
|
powerLevelsContent = toModel
|
||||||
|
toModel != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(100, powerLevelsContent?.eventsDefault, "Space-rooms should be created with a power level for events_default of 100")
|
||||||
|
|
||||||
|
val guestAccess = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS)?.content
|
||||||
|
?.toModel<RoomGuestAccessContent>()?.guestAccess
|
||||||
|
|
||||||
|
assertEquals(GuestAccess.CanJoin, guestAccess, "Public space room should be peekable by guest")
|
||||||
|
|
||||||
|
val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content
|
||||||
|
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
||||||
|
|
||||||
|
assertEquals(RoomHistoryVisibility.WORLD_READABLE, historyVisibility, "Public space room should be world readable")
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJoinSimplePublicSpace() {
|
||||||
|
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||||
|
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
|
||||||
|
|
||||||
|
val roomName = "My Space"
|
||||||
|
val topic = "A public space for test"
|
||||||
|
val spaceId: String
|
||||||
|
runBlocking {
|
||||||
|
spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
|
||||||
|
// wait a bit to let the summary update it self :/
|
||||||
|
delay(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to join from bob, it's a public space no need to invite
|
||||||
|
|
||||||
|
val joinResult: JoinSpaceResult
|
||||||
|
runBlocking {
|
||||||
|
joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(JoinSpaceResult.Success, joinResult)
|
||||||
|
|
||||||
|
val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
|
||||||
|
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||||
|
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(aliceSession)
|
||||||
|
commonTestHelper.signOutAndClose(bobSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimplePublicSpaceWithChildren() {
|
||||||
|
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||||
|
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
|
||||||
|
|
||||||
|
val roomName = "My Space"
|
||||||
|
val topic = "A public space for test"
|
||||||
|
|
||||||
|
val spaceId: String = runBlocking { aliceSession.spaceService().createSpace(roomName, topic, null, true) }
|
||||||
|
val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
|
||||||
|
|
||||||
|
// create a room
|
||||||
|
val firstChild: String = runBlocking {
|
||||||
|
aliceSession.createRoom(CreateRoomParams().apply {
|
||||||
|
this.name = "FirstRoom"
|
||||||
|
this.topic = "Description of first room"
|
||||||
|
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val secondChild: String = runBlocking {
|
||||||
|
aliceSession.createRoom(CreateRoomParams().apply {
|
||||||
|
this.name = "SecondRoom"
|
||||||
|
this.topic = "Description of second room"
|
||||||
|
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to join from bob, it's a public space no need to invite
|
||||||
|
|
||||||
|
val joinResult = runBlocking {
|
||||||
|
bobSession.spaceService().joinSpace(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(JoinSpaceResult.Success, joinResult)
|
||||||
|
|
||||||
|
val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
|
||||||
|
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
|
||||||
|
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
|
||||||
|
|
||||||
|
// check if bob has joined automatically the first room
|
||||||
|
|
||||||
|
val bobMembershipFirstRoom = bobSession.getRoom(firstChild)?.roomSummary()?.membership
|
||||||
|
assertEquals(Membership.JOIN, bobMembershipFirstRoom, "Bob should have joined this room")
|
||||||
|
RoomSummaryQueryParams.Builder()
|
||||||
|
|
||||||
|
val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams {
|
||||||
|
this.roomId = QueryStringValue.Equals(spaceId)
|
||||||
|
this.memberships = listOf(Membership.JOIN)
|
||||||
|
}).firstOrNull()
|
||||||
|
|
||||||
|
assertEquals(2, spaceSummaryBobPov?.spaceChildren?.size ?: -1, "Unexpected number of children")
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(aliceSession)
|
||||||
|
commonTestHelper.signOutAndClose(bobSession)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,414 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.session.space
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
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.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SpaceHierarchyTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val commonTestHelper = CommonTestHelper(context())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createCanonicalChildRelation() {
|
||||||
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
val spaceName = "My Space"
|
||||||
|
val topic = "A public space for test"
|
||||||
|
val spaceId: String
|
||||||
|
runBlocking {
|
||||||
|
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
|
||||||
|
// wait a bit to let the summary update it self :/
|
||||||
|
delay(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||||
|
|
||||||
|
val roomId = runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = "General" })
|
||||||
|
}
|
||||||
|
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
syncedSpace!!.addChildren(roomId, viaServers, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(9000)
|
||||||
|
|
||||||
|
val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
|
||||||
|
val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
|
||||||
|
|
||||||
|
parents?.forEach {
|
||||||
|
Log.d("## TEST", "parent : $it")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotNull(parents)
|
||||||
|
assertEquals(1, parents.size)
|
||||||
|
assertEquals(spaceName, parents.first().roomSummary?.name)
|
||||||
|
|
||||||
|
assertNotNull(canonicalParents)
|
||||||
|
assertEquals(1, canonicalParents.size)
|
||||||
|
assertEquals(spaceName, canonicalParents.first().roomSummary?.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCreateChildRelations() {
|
||||||
|
val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true))
|
||||||
|
val spaceName = "My Space"
|
||||||
|
val topic = "A public space for test"
|
||||||
|
Log.d("## TEST", "Before")
|
||||||
|
val spaceId = runBlocking {
|
||||||
|
session.spaceService().createSpace(spaceName, topic, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}")
|
||||||
|
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||||
|
|
||||||
|
val children = listOf("General" to true /*canonical*/, "Random" to false)
|
||||||
|
|
||||||
|
val roomIdList = children.map {
|
||||||
|
runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = it.first })
|
||||||
|
} to it.second
|
||||||
|
}
|
||||||
|
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
roomIdList.forEach { entry ->
|
||||||
|
syncedSpace!!.addChildren(entry.first, viaServers, null, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
roomIdList.forEach {
|
||||||
|
session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers)
|
||||||
|
}
|
||||||
|
delay(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
roomIdList.forEach {
|
||||||
|
val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents
|
||||||
|
val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
|
||||||
|
|
||||||
|
assertNotNull(parents)
|
||||||
|
assertEquals(1, parents.size, "Unexpected number of parent")
|
||||||
|
assertEquals(spaceName, parents.first().roomSummary?.name, "Unexpected parent name ")
|
||||||
|
assertEquals(if (it.second) 1 else 0, canonicalParents?.size ?: 0, "Parent of ${it.first} should be canonical ${it.second}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFilteringBySpace() {
|
||||||
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
|
||||||
|
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||||
|
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("A2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
|
||||||
|
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("B2", true, true),
|
||||||
|
Triple("B3", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||||
|
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("C2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
// add C as a subspace of A
|
||||||
|
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
runBlocking {
|
||||||
|
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||||
|
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create orphan rooms
|
||||||
|
|
||||||
|
val orphan1 = runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = "O1" })
|
||||||
|
}
|
||||||
|
val orphan2 = runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = "O2" })
|
||||||
|
}
|
||||||
|
|
||||||
|
val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
|
||||||
|
|
||||||
|
assertEquals(9, allRooms.size, "Unexpected number of rooms")
|
||||||
|
|
||||||
|
val orphans = session.getFlattenRoomSummaryChildrenOf(null)
|
||||||
|
|
||||||
|
assertEquals(2, orphans.size, "Unexpected number of orphan rooms")
|
||||||
|
assertTrue(orphans.indexOfFirst { it.roomId == orphan1 } != -1, "O1 should be an orphan")
|
||||||
|
assertTrue(orphans.indexOfFirst { it.roomId == orphan2 } != -1, "O2 should be an orphan ${orphans.map { it.name }}")
|
||||||
|
|
||||||
|
val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
|
||||||
|
|
||||||
|
assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
|
||||||
|
|
||||||
|
// Add a non canonical child and check that it does not appear as orphan
|
||||||
|
val a3 = runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = "A3" })
|
||||||
|
}
|
||||||
|
runBlocking {
|
||||||
|
spaceA!!.addChildren(a3, viaServers, null, false)
|
||||||
|
delay(400)
|
||||||
|
// here we do not set the parent!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val orphansUpdate = session.getFlattenRoomSummaryChildrenOf(null)
|
||||||
|
assertEquals(2, orphansUpdate.size, "Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBreakCycle() {
|
||||||
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
|
||||||
|
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||||
|
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("A2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||||
|
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("C2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
// add C as a subspace of A
|
||||||
|
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
runBlocking {
|
||||||
|
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||||
|
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add back A as subspace of C
|
||||||
|
runBlocking {
|
||||||
|
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
|
||||||
|
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
|
||||||
|
// A -> C -> A
|
||||||
|
|
||||||
|
val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
|
||||||
|
|
||||||
|
assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms ${aChildren.map { it.name }}")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
|
||||||
|
assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLiveFlatChildren() {
|
||||||
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
|
||||||
|
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||||
|
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("A2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
|
||||||
|
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("B2", true, true),
|
||||||
|
Triple("B3", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
// add B as a subspace of A
|
||||||
|
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
runBlocking {
|
||||||
|
spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
|
||||||
|
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val flatAChildren = runBlocking(Dispatchers.Main) {
|
||||||
|
session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
|
||||||
|
val childObserver = object : Observer<List<RoomSummary>> {
|
||||||
|
override fun onChanged(children: List<RoomSummary>?) {
|
||||||
|
// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
|
||||||
|
System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
|
||||||
|
if (children?.indexOfFirst { it.name == "C1" } != -1
|
||||||
|
&& children?.indexOfFirst { it.name == "C2" } != -1
|
||||||
|
) {
|
||||||
|
// B1 has been added live!
|
||||||
|
latch.countDown()
|
||||||
|
flatAChildren.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||||
|
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("C2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
// add C as subspace of B
|
||||||
|
runBlocking {
|
||||||
|
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||||
|
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// C1 and C2 should be in flatten child of A now
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test part one of the rooms
|
||||||
|
|
||||||
|
val bRoomId = spaceBInfo.roomIds.first()
|
||||||
|
val bRoom = session.getRoom(bRoomId)
|
||||||
|
|
||||||
|
commonTestHelper.waitWithLatch { latch ->
|
||||||
|
|
||||||
|
val childObserver = object : Observer<List<RoomSummary>> {
|
||||||
|
override fun onChanged(children: List<RoomSummary>?) {
|
||||||
|
System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
|
||||||
|
if (children?.any { it.roomId == bRoomId } == false) {
|
||||||
|
// B1 has been added live!
|
||||||
|
latch.countDown()
|
||||||
|
flatAChildren.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// part from b room
|
||||||
|
runBlocking {
|
||||||
|
bRoom!!.leave(null)
|
||||||
|
}
|
||||||
|
// The room should have disapear from flat children
|
||||||
|
GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TestSpaceCreationResult(
|
||||||
|
val spaceId: String,
|
||||||
|
val roomIds: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createPublicSpace(session: Session,
|
||||||
|
spaceName: String,
|
||||||
|
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||||
|
/** Name, auto-join, canonical*/
|
||||||
|
): TestSpaceCreationResult {
|
||||||
|
val spaceId = runBlocking {
|
||||||
|
session.spaceService().createSpace(spaceName, "Test Topic", null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
|
||||||
|
val roomIds =
|
||||||
|
childInfo.map { entry ->
|
||||||
|
runBlocking {
|
||||||
|
session.createRoom(CreateRoomParams().apply { name = entry.first })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roomIds.forEachIndexed { index, roomId ->
|
||||||
|
runBlocking {
|
||||||
|
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||||
|
val canonical = childInfo[index].third
|
||||||
|
if (canonical != null) {
|
||||||
|
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TestSpaceCreationResult(spaceId, roomIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRootSpaces() {
|
||||||
|
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||||
|
|
||||||
|
val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
|
||||||
|
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("A2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
|
||||||
|
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("B2", true, true),
|
||||||
|
Triple("B3", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
|
||||||
|
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||||
|
Triple("C2", true, true)
|
||||||
|
))
|
||||||
|
|
||||||
|
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||||
|
|
||||||
|
// add C as subspace of B
|
||||||
|
runBlocking {
|
||||||
|
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||||
|
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(2000)
|
||||||
|
// + A
|
||||||
|
// a1, a2
|
||||||
|
// + B
|
||||||
|
// b1, b2, b3
|
||||||
|
// + C
|
||||||
|
// + c1, c2
|
||||||
|
|
||||||
|
val rootSpaces = session.spaceService().getRootSpaceSummaries()
|
||||||
|
|
||||||
|
assertEquals(2, rootSpaces.size, "Unexpected number of root spaces ${rootSpaces.map { it.name }}")
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,10 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.auth.data
|
package org.matrix.android.sdk.api.auth.data
|
||||||
|
|
||||||
sealed class LoginFlowResult {
|
data class LoginFlowResult(
|
||||||
data class Success(
|
val supportedLoginTypes: List<String>,
|
||||||
val supportedLoginTypes: List<String>,
|
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
||||||
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
val isLoginAndRegistrationSupported: Boolean,
|
val homeServerUrl: String,
|
||||||
val homeServerUrl: String,
|
val isOutdatedHomeserver: Boolean
|
||||||
val isOutdatedHomeserver: Boolean
|
)
|
||||||
) : LoginFlowResult()
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ interface RegistrationWizard {
|
||||||
|
|
||||||
suspend fun getRegistrationFlow(): RegistrationResult
|
suspend fun getRegistrationFlow(): RegistrationResult
|
||||||
|
|
||||||
suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
|
suspend fun createAccount(userName: String?,
|
||||||
|
password: String?,
|
||||||
|
initialDeviceDisplayName: String?): RegistrationResult
|
||||||
|
|
||||||
suspend fun performReCaptcha(response: String): RegistrationResult
|
suspend fun performReCaptcha(response: String): RegistrationResult
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ import java.io.IOException
|
||||||
*/
|
*/
|
||||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
|
||||||
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
|
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
|
||||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.query
|
||||||
|
|
||||||
|
sealed class ActiveSpaceFilter {
|
||||||
|
object None : ActiveSpaceFilter()
|
||||||
|
data class ActiveSpace(val currentSpaceId: String?) : ActiveSpaceFilter()
|
||||||
|
data class ExcludeSpace(val spaceId: String) : ActiveSpaceFilter()
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||||
import org.matrix.android.sdk.api.session.sync.FilterService
|
import org.matrix.android.sdk.api.session.sync.FilterService
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
|
@ -227,6 +228,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun thirdPartyService(): ThirdPartyService
|
fun thirdPartyService(): ThirdPartyService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the space service associated with the session
|
||||||
|
*/
|
||||||
|
fun spaceService(): SpaceService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
@ -249,13 +255,13 @@ interface Session :
|
||||||
/**
|
/**
|
||||||
* A global session listener to get notified for some events.
|
* A global session listener to get notified for some events.
|
||||||
*/
|
*/
|
||||||
interface Listener {
|
interface Listener : SessionLifecycleObserver {
|
||||||
/**
|
/**
|
||||||
* Possible cases:
|
* Possible cases:
|
||||||
* - The access token is not valid anymore,
|
* - The access token is not valid anymore,
|
||||||
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
||||||
*/
|
*/
|
||||||
fun onGlobalError(globalError: GlobalError)
|
fun onGlobalError(session: Session, globalError: GlobalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sharedSecretStorageService: SharedSecretStorageService
|
val sharedSecretStorageService: SharedSecretStorageService
|
||||||
|
|
|
@ -14,20 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session
|
package org.matrix.android.sdk.api.session
|
||||||
|
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This defines methods associated with some lifecycle events of a session.
|
* This defines methods associated with some lifecycle events of a session.
|
||||||
* A list of SessionLifecycle will be injected into [DefaultSession]
|
|
||||||
*/
|
*/
|
||||||
internal interface SessionLifecycleObserver {
|
interface SessionLifecycleObserver {
|
||||||
/*
|
/*
|
||||||
Called when the session is opened
|
Called when the session is opened
|
||||||
*/
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
fun onSessionStarted() {
|
fun onSessionStarted(session: Session) {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ internal interface SessionLifecycleObserver {
|
||||||
Called when the session is cleared
|
Called when the session is cleared
|
||||||
*/
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
fun onClearCache() {
|
fun onClearCache(session: Session) {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ internal interface SessionLifecycleObserver {
|
||||||
Called when the session is closed
|
Called when the session is closed
|
||||||
*/
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
fun onSessionStopped() {
|
fun onSessionStopped(session: Session) {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -53,6 +53,12 @@ object EventType {
|
||||||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||||
|
|
||||||
|
// const val STATE_SPACE_CHILD = "m.space.child"
|
||||||
|
const val STATE_SPACE_CHILD = "org.matrix.msc1772.space.child"
|
||||||
|
|
||||||
|
// const val STATE_SPACE_PARENT = "m.space.parent"
|
||||||
|
const val STATE_SPACE_PARENT = "org.matrix.msc1772.space.parent"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note that this Event has been deprecated, see
|
* Note that this Event has been deprecated, see
|
||||||
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
|
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
|
||||||
|
@ -75,6 +81,7 @@ object EventType {
|
||||||
const val CALL_NEGOTIATE = "m.call.negotiate"
|
const val CALL_NEGOTIATE = "m.call.negotiate"
|
||||||
const val CALL_REJECT = "m.call.reject"
|
const val CALL_REJECT = "m.call.reject"
|
||||||
const val CALL_HANGUP = "m.call.hangup"
|
const val CALL_HANGUP = "m.call.hangup"
|
||||||
|
|
||||||
// This type is not processed by the client, just sent to the server
|
// This type is not processed by the client, just sent to the server
|
||||||
const val CALL_REPLACES = "m.call.replaces"
|
const val CALL_REPLACES = "m.call.replaces"
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,19 @@ import java.io.File
|
||||||
*/
|
*/
|
||||||
interface FileService {
|
interface FileService {
|
||||||
|
|
||||||
enum class FileState {
|
sealed class FileState {
|
||||||
IN_CACHE,
|
/**
|
||||||
DOWNLOADING,
|
* The original file is in cache, but the decrypted files can be deleted for security reason.
|
||||||
UNKNOWN
|
* To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again
|
||||||
|
* @param decryptedFileInCache true if the decrypted file is available. Always true for clear files.
|
||||||
|
*/
|
||||||
|
data class InCache(val decryptedFileInCache: Boolean) : FileState()
|
||||||
|
object Downloading : FileState()
|
||||||
|
object Unknown : FileState()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a file.
|
* Download a file if necessary and ensure that if the file is encrypted, the file is decrypted.
|
||||||
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
|
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
|
||||||
*/
|
*/
|
||||||
suspend fun downloadFile(fileName: String,
|
suspend fun downloadFile(fileName: String,
|
||||||
|
|
|
@ -66,12 +66,13 @@ interface PushersService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directly ask the push gateway to send a push to this device
|
* Directly ask the push gateway to send a push to this device
|
||||||
|
* If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
|
||||||
|
* In case of error, PusherRejected will be thrown. In this case it means that the pushkey is not valid.
|
||||||
|
*
|
||||||
* @param url the push gateway url (full path)
|
* @param url the push gateway url (full path)
|
||||||
* @param appId the application id
|
* @param appId the application id
|
||||||
* @param pushkey the FCM token
|
* @param pushkey the FCM token
|
||||||
* @param eventId the eventId which will be sent in the Push message. Use a fake eventId.
|
* @param eventId the eventId which will be sent in the Push message. Use a fake eventId.
|
||||||
* @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
|
|
||||||
* In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid.
|
|
||||||
*/
|
*/
|
||||||
suspend fun testPush(url: String,
|
suspend fun testPush(url: String,
|
||||||
appId: String,
|
appId: String,
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||||
import org.matrix.android.sdk.api.session.search.SearchResult
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,4 +92,9 @@ interface Room :
|
||||||
beforeLimit: Int,
|
beforeLimit: Int,
|
||||||
afterLimit: Int,
|
afterLimit: Int,
|
||||||
includeProfile: Boolean): SearchResult
|
includeProfile: Boolean): SearchResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this room as a Space, if the type is correct.
|
||||||
|
*/
|
||||||
|
fun asSpace(): Space?
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,14 @@ package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
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.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.peeking.PeekResult
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||||
|
|
||||||
|
@ -38,22 +37,19 @@ interface RoomService {
|
||||||
/**
|
/**
|
||||||
* Create a room asynchronously
|
* Create a room asynchronously
|
||||||
*/
|
*/
|
||||||
fun createRoom(createRoomParams: CreateRoomParams,
|
suspend fun createRoom(createRoomParams: CreateRoomParams): String
|
||||||
callback: MatrixCallback<String>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
|
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
|
||||||
*/
|
*/
|
||||||
fun createDirectRoom(otherUserId: String,
|
suspend fun createDirectRoom(otherUserId: String): String {
|
||||||
callback: MatrixCallback<String>): Cancelable {
|
|
||||||
return createRoom(
|
return createRoom(
|
||||||
CreateRoomParams()
|
CreateRoomParams()
|
||||||
.apply {
|
.apply {
|
||||||
invitedUserIds.add(otherUserId)
|
invitedUserIds.add(otherUserId)
|
||||||
setDirectMessage()
|
setDirectMessage()
|
||||||
enableEncryptionIfInvitedUsersSupportIt = true
|
enableEncryptionIfInvitedUsersSupportIt = true
|
||||||
},
|
}
|
||||||
callback
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +59,9 @@ interface RoomService {
|
||||||
* @param reason optional reason for joining the room
|
* @param reason optional reason for joining the room
|
||||||
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
|
||||||
*/
|
*/
|
||||||
fun joinRoom(roomIdOrAlias: String,
|
suspend fun joinRoom(roomIdOrAlias: String,
|
||||||
reason: String? = null,
|
reason: String? = null,
|
||||||
viaServers: List<String> = emptyList(),
|
viaServers: List<String> = emptyList())
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a room from a roomId
|
* Get a room from a roomId
|
||||||
|
@ -112,20 +107,18 @@ interface RoomService {
|
||||||
* Inform the Matrix SDK that a room is displayed.
|
* Inform the Matrix SDK that a room is displayed.
|
||||||
* The SDK will update the breadcrumbs in the user account data
|
* The SDK will update the breadcrumbs in the user account data
|
||||||
*/
|
*/
|
||||||
fun onRoomDisplayed(roomId: String): Cancelable
|
suspend fun onRoomDisplayed(roomId: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all rooms as read
|
* Mark all rooms as read
|
||||||
*/
|
*/
|
||||||
fun markAllAsRead(roomIds: List<String>,
|
suspend fun markAllAsRead(roomIds: List<String>)
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a room alias to a room ID.
|
* Resolve a room alias to a room ID.
|
||||||
*/
|
*/
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
suspend fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean,
|
searchOnServer: Boolean): Optional<RoomAliasDescription>
|
||||||
callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a room alias
|
* Delete a room alias
|
||||||
|
@ -172,26 +165,28 @@ interface RoomService {
|
||||||
/**
|
/**
|
||||||
* Get some state events about a room
|
* Get some state events about a room
|
||||||
*/
|
*/
|
||||||
fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>)
|
suspend fun getRoomState(roomId: String): List<Event>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this if you want to get information from a room that you are not yet in (or invited)
|
* Use this if you want to get information from a room that you are not yet in (or invited)
|
||||||
* It might be possible to get some information on this room if it is public or if guest access is allowed
|
* It might be possible to get some information on this room if it is public or if guest access is allowed
|
||||||
* This call will try to gather some information on this room, but it could fail and get nothing more
|
* This call will try to gather some information on this room, but it could fail and get nothing more
|
||||||
*/
|
*/
|
||||||
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
suspend fun peekRoom(roomIdOrAlias: String): PeekResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Doc
|
* TODO Doc
|
||||||
*/
|
*/
|
||||||
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
|
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<PagedList<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Doc
|
* TODO Doc
|
||||||
*/
|
*/
|
||||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
|
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
||||||
|
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Doc
|
* TODO Doc
|
||||||
|
@ -205,4 +200,12 @@ interface RoomService {
|
||||||
.setEnablePlaceholders(false)
|
.setEnablePlaceholders(false)
|
||||||
.setPrefetchDistance(10)
|
.setPrefetchDistance(10)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List<Membership> = Membership.activeMemberships()) : List<RoomSummary>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the children of this space, as LiveData
|
||||||
|
*/
|
||||||
|
fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?,
|
||||||
|
memberships: List<Membership> = Membership.activeMemberships()): LiveData<List<RoomSummary>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -12,14 +12,12 @@
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.grouplist
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
enum class RoomSortOrder {
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
NAME,
|
||||||
|
ACTIVITY,
|
||||||
sealed class GroupListAction : VectorViewModelAction {
|
NONE
|
||||||
data class SelectGroup(val groupSummary: GroupSummary) : GroupListAction()
|
|
||||||
}
|
}
|
|
@ -16,15 +16,35 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room
|
package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
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.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||||
|
|
||||||
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||||
return RoomSummaryQueryParams.Builder().apply(init).build()
|
return RoomSummaryQueryParams.Builder().apply(init).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams {
|
||||||
|
return RoomSummaryQueryParams.Builder()
|
||||||
|
.apply(init)
|
||||||
|
.apply {
|
||||||
|
includeType = listOf(RoomType.SPACE)
|
||||||
|
excludeType = null
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class RoomCategoryFilter {
|
||||||
|
ONLY_DM,
|
||||||
|
ONLY_ROOMS,
|
||||||
|
ALL
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can be used to filter room summaries to use with:
|
* This class can be used to filter room summaries to use with:
|
||||||
* [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
|
* [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
|
||||||
|
@ -35,7 +55,11 @@ data class RoomSummaryQueryParams(
|
||||||
val canonicalAlias: QueryStringValue,
|
val canonicalAlias: QueryStringValue,
|
||||||
val memberships: List<Membership>,
|
val memberships: List<Membership>,
|
||||||
val roomCategoryFilter: RoomCategoryFilter?,
|
val roomCategoryFilter: RoomCategoryFilter?,
|
||||||
val roomTagQueryFilter: RoomTagQueryFilter?
|
val roomTagQueryFilter: RoomTagQueryFilter?,
|
||||||
|
val excludeType: List<String?>?,
|
||||||
|
val includeType: List<String?>?,
|
||||||
|
val activeSpaceId: ActiveSpaceFilter?,
|
||||||
|
var activeGroupId: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -46,6 +70,10 @@ data class RoomSummaryQueryParams(
|
||||||
var memberships: List<Membership> = Membership.all()
|
var memberships: List<Membership> = Membership.all()
|
||||||
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
||||||
var roomTagQueryFilter: RoomTagQueryFilter? = null
|
var roomTagQueryFilter: RoomTagQueryFilter? = null
|
||||||
|
var excludeType: List<String?>? = listOf(RoomType.SPACE)
|
||||||
|
var includeType: List<String?>? = null
|
||||||
|
var activeSpaceId: ActiveSpaceFilter = ActiveSpaceFilter.None
|
||||||
|
var activeGroupId: String? = null
|
||||||
|
|
||||||
fun build() = RoomSummaryQueryParams(
|
fun build() = RoomSummaryQueryParams(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
@ -53,7 +81,11 @@ data class RoomSummaryQueryParams(
|
||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
memberships = memberships,
|
memberships = memberships,
|
||||||
roomCategoryFilter = roomCategoryFilter,
|
roomCategoryFilter = roomCategoryFilter,
|
||||||
roomTagQueryFilter = roomTagQueryFilter
|
roomTagQueryFilter = roomTagQueryFilter,
|
||||||
|
excludeType = excludeType,
|
||||||
|
includeType = includeType,
|
||||||
|
activeSpaceId = activeSpaceId,
|
||||||
|
activeGroupId = activeGroupId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,16 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
interface UpdatableFilterLivePageResult {
|
interface UpdatableLivePageResult {
|
||||||
val livePagedList: LiveData<PagedList<RoomSummary>>
|
val livePagedList: LiveData<PagedList<RoomSummary>>
|
||||||
|
|
||||||
fun updateQuery(queryParams: RoomSummaryQueryParams)
|
fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
|
||||||
|
|
||||||
|
val liveBoundaries: LiveData<ResultBoundaries>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ResultBoundaries(
|
||||||
|
val frontLoaded: Boolean = false,
|
||||||
|
val endLoaded: Boolean = false,
|
||||||
|
val zeroItemLoaded: Boolean = false
|
||||||
|
)
|
|
@ -17,7 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.room.alias
|
package org.matrix.android.sdk.api.session.room.alias
|
||||||
|
|
||||||
sealed class RoomAliasError : Throwable() {
|
sealed class RoomAliasError : Throwable() {
|
||||||
object AliasEmpty : RoomAliasError()
|
object AliasIsBlank : RoomAliasError()
|
||||||
object AliasNotAvailable : RoomAliasError()
|
object AliasNotAvailable : RoomAliasError()
|
||||||
object AliasInvalid : RoomAliasError()
|
object AliasInvalid : RoomAliasError()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||||
|
|
||||||
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
||||||
object CreatedWithTimeout : CreateRoomFailure()
|
data class CreatedWithTimeout(val roomID: String) : CreateRoomFailure()
|
||||||
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
|
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
|
||||||
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
|
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,43 +28,43 @@ data class PowerLevelsContent(
|
||||||
/**
|
/**
|
||||||
* The level required to ban a user. Defaults to 50 if unspecified.
|
* The level required to ban a user. Defaults to 50 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
@Json(name = "ban") val ban: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The level required to kick a user. Defaults to 50 if unspecified.
|
* The level required to kick a user. Defaults to 50 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
@Json(name = "kick") val kick: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The level required to invite a user. Defaults to 50 if unspecified.
|
* The level required to invite a user. Defaults to 50 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
@Json(name = "invite") val invite: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The level required to redact an event. Defaults to 50 if unspecified.
|
* The level required to redact an event. Defaults to 50 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
@Json(name = "redact") val redact: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
|
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
@Json(name = "events_default") val eventsDefault: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The level required to send specific event types. This is a mapping from event type to power level required.
|
* The level required to send specific event types. This is a mapping from event type to power level required.
|
||||||
*/
|
*/
|
||||||
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
@Json(name = "events") val events: Map<String, Int>? = null,
|
||||||
/**
|
/**
|
||||||
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
|
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
@Json(name = "users_default") val usersDefault: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The power levels for specific users. This is a mapping from user_id to power level for that user.
|
* The power levels for specific users. This is a mapping from user_id to power level for that user.
|
||||||
*/
|
*/
|
||||||
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
@Json(name = "users") val users: Map<String, Int>? = null,
|
||||||
/**
|
/**
|
||||||
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
|
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
|
||||||
*/
|
*/
|
||||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
@Json(name = "state_default") val stateDefault: Int? = null,
|
||||||
/**
|
/**
|
||||||
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
|
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
|
||||||
*/
|
*/
|
||||||
@Json(name = "notifications") val notifications: Map<String, Any> = emptyMap()
|
@Json(name = "notifications") val notifications: Map<String, Any>? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Return a copy of this content with a new power level for the specified user
|
* Return a copy of this content with a new power level for the specified user
|
||||||
|
@ -74,7 +74,7 @@ data class PowerLevelsContent(
|
||||||
*/
|
*/
|
||||||
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
|
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
|
||||||
return copy(
|
return copy(
|
||||||
users = users.toMutableMap().apply {
|
users = users.orEmpty().toMutableMap().apply {
|
||||||
if (powerLevel == null || powerLevel == usersDefault) {
|
if (powerLevel == null || powerLevel == usersDefault) {
|
||||||
remove(userId)
|
remove(userId)
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,7 +91,7 @@ data class PowerLevelsContent(
|
||||||
* @return the level, default to Moderator if the key is not found
|
* @return the level, default to Moderator if the key is not found
|
||||||
*/
|
*/
|
||||||
fun notificationLevel(key: String): Int {
|
fun notificationLevel(key: String): Int {
|
||||||
return when (val value = notifications[key]) {
|
return when (val value = notifications.orEmpty()[key]) {
|
||||||
// the first implementation was a string value
|
// the first implementation was a string value
|
||||||
is String -> value.toInt()
|
is String -> value.toInt()
|
||||||
is Double -> value.toInt()
|
is Double -> value.toInt()
|
||||||
|
@ -107,3 +107,12 @@ data class PowerLevelsContent(
|
||||||
const val NOTIFICATIONS_ROOM_KEY = "room"
|
const val NOTIFICATIONS_ROOM_KEY = "room"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to default value, defined in the Matrix specification
|
||||||
|
fun PowerLevelsContent.banOrDefault() = ban ?: Role.Moderator.value
|
||||||
|
fun PowerLevelsContent.kickOrDefault() = kick ?: Role.Moderator.value
|
||||||
|
fun PowerLevelsContent.inviteOrDefault() = invite ?: Role.Moderator.value
|
||||||
|
fun PowerLevelsContent.redactOrDefault() = redact ?: Role.Moderator.value
|
||||||
|
fun PowerLevelsContent.eventsDefaultOrDefault() = eventsDefault ?: Role.Default.value
|
||||||
|
fun PowerLevelsContent.usersDefaultOrDefault() = usersDefault ?: Role.Default.value
|
||||||
|
fun PowerLevelsContent.stateDefaultOrDefault() = stateDefault ?: Role.Moderator.value
|
||||||
|
|
|
@ -40,7 +40,7 @@ data class RoomGuestAccessContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonClass(generateAdapter = false)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class GuestAccess {
|
enum class GuestAccess(val value: String) {
|
||||||
@Json(name = "can_join") CanJoin,
|
@Json(name = "can_join") CanJoin("can_join"),
|
||||||
@Json(name = "forbidden") Forbidden
|
@Json(name = "forbidden") Forbidden("forbidden")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,35 +16,31 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.model
|
package org.matrix.android.sdk.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = false)
|
|
||||||
enum class RoomHistoryVisibility {
|
enum class RoomHistoryVisibility {
|
||||||
/**
|
/**
|
||||||
* All events while this is the m.room.history_visibility value may be shared by any
|
* All events while this is the m.room.history_visibility value may be shared by any
|
||||||
* participating homeserver with anyone, regardless of whether they have ever joined the room.
|
* participating homeserver with anyone, regardless of whether they have ever joined the room.
|
||||||
*/
|
*/
|
||||||
@Json(name = "world_readable") WORLD_READABLE,
|
WORLD_READABLE,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previous events are always accessible to newly joined members. All events in the
|
* Previous events are always accessible to newly joined members. All events in the
|
||||||
* room are accessible, even those sent when the member was not a part of the room.
|
* room are accessible, even those sent when the member was not a part of the room.
|
||||||
*/
|
*/
|
||||||
@Json(name = "shared") SHARED,
|
SHARED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events are accessible to newly joined members from the point they were invited onwards.
|
* Events are accessible to newly joined members from the point they were invited onwards.
|
||||||
* Events stop being accessible when the member's state changes to something other than invite or join.
|
* Events stop being accessible when the member's state changes to something other than invite or join.
|
||||||
*/
|
*/
|
||||||
@Json(name = "invited") INVITED,
|
INVITED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events are accessible to newly joined members from the point they joined the room onwards.
|
* Events are accessible to newly joined members from the point they joined the room onwards.
|
||||||
* Events stop being accessible when the member's state changes to something other than join.
|
* Events stop being accessible when the member's state changes to something other than join.
|
||||||
*/
|
*/
|
||||||
@Json(name = "joined") JOINED
|
JOINED
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,10 @@ import com.squareup.moshi.JsonClass
|
||||||
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
|
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = false)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RoomJoinRules {
|
enum class RoomJoinRules(val value: String) {
|
||||||
@Json(name = "public") PUBLIC,
|
@Json(name = "public") PUBLIC("public"),
|
||||||
@Json(name = "invite") INVITE,
|
@Json(name = "invite") INVITE("invite"),
|
||||||
@Json(name = "knock") KNOCK,
|
@Json(name = "knock") KNOCK("knock"),
|
||||||
@Json(name = "private") PRIVATE
|
@Json(name = "private") PRIVATE("private"),
|
||||||
|
@Json(name = "restricted") RESTRICTED("restricted")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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 com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RoomJoinRulesAllowEntry(
|
||||||
|
/**
|
||||||
|
* space: The room ID of the space to check the membership of.
|
||||||
|
*/
|
||||||
|
@Json(name = "space") val spaceID: String,
|
||||||
|
/**
|
||||||
|
* via: A list of servers which may be used to peek for membership of the space.
|
||||||
|
*/
|
||||||
|
@Json(name = "via") val via: List<String>
|
||||||
|
)
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,14 +27,19 @@ import timber.log.Timber
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RoomJoinRulesContent(
|
data class RoomJoinRulesContent(
|
||||||
@Json(name = "join_rule") val _joinRules: String? = null
|
@Json(name = "join_rule") val _joinRules: String? = null,
|
||||||
|
/**
|
||||||
|
* If the allow key is an empty list (or not a list at all), then the room reverts to standard public join rules
|
||||||
|
*/
|
||||||
|
@Json(name = "allow") val allowList: List<RoomJoinRulesAllowEntry>? = null
|
||||||
) {
|
) {
|
||||||
val joinRules: RoomJoinRules? = when (_joinRules) {
|
val joinRules: RoomJoinRules? = when (_joinRules) {
|
||||||
"public" -> RoomJoinRules.PUBLIC
|
"public" -> RoomJoinRules.PUBLIC
|
||||||
"invite" -> RoomJoinRules.INVITE
|
"invite" -> RoomJoinRules.INVITE
|
||||||
"knock" -> RoomJoinRules.KNOCK
|
"knock" -> RoomJoinRules.KNOCK
|
||||||
"private" -> RoomJoinRules.PRIVATE
|
"private" -> RoomJoinRules.PRIVATE
|
||||||
else -> {
|
"restricted" -> RoomJoinRules.RESTRICTED
|
||||||
|
else -> {
|
||||||
Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
|
Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ data class RoomSummary constructor(
|
||||||
val canonicalAlias: String? = null,
|
val canonicalAlias: String? = null,
|
||||||
val aliases: List<String> = emptyList(),
|
val aliases: List<String> = emptyList(),
|
||||||
val isDirect: Boolean = false,
|
val isDirect: Boolean = false,
|
||||||
|
val directUserId: String? = null,
|
||||||
val joinedMembersCount: Int? = 0,
|
val joinedMembersCount: Int? = 0,
|
||||||
val invitedMembersCount: Int? = 0,
|
val invitedMembersCount: Int? = 0,
|
||||||
val latestPreviewableEvent: TimelineEvent? = null,
|
val latestPreviewableEvent: TimelineEvent? = null,
|
||||||
|
@ -48,6 +49,7 @@ data class RoomSummary constructor(
|
||||||
val hasUnreadMessages: Boolean = false,
|
val hasUnreadMessages: Boolean = false,
|
||||||
val hasUnreadContentMessages: Boolean = false,
|
val hasUnreadContentMessages: Boolean = false,
|
||||||
val hasUnreadOriginalContentMessages: Boolean = false,
|
val hasUnreadOriginalContentMessages: Boolean = false,
|
||||||
|
val unreadCount: Int? = 0,
|
||||||
val markedUnread: Boolean = false,
|
val markedUnread: Boolean = false,
|
||||||
val tags: List<RoomTag> = emptyList(),
|
val tags: List<RoomTag> = emptyList(),
|
||||||
val membership: Membership = Membership.NONE,
|
val membership: Membership = Membership.NONE,
|
||||||
|
@ -60,7 +62,11 @@ data class RoomSummary constructor(
|
||||||
val inviterId: String? = null,
|
val inviterId: String? = null,
|
||||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
|
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
|
||||||
val hasFailedSending: Boolean = false
|
val hasFailedSending: Boolean = false,
|
||||||
|
val roomType: String? = null,
|
||||||
|
val spaceParents: List<SpaceParentInfo>? = null,
|
||||||
|
val spaceChildren: List<SpaceChildInfo>? = null,
|
||||||
|
val flattenParentIds: List<String> = emptyList()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isVersioned: Boolean
|
val isVersioned: Boolean
|
||||||
|
|
|
@ -47,7 +47,7 @@ data class RoomThirdPartyInviteContent(
|
||||||
/**
|
/**
|
||||||
* Keys with which the token may be signed.
|
* Keys with which the token may be signed.
|
||||||
*/
|
*/
|
||||||
@Json(name = "public_keys") val publicKeys: List<PublicKeys>? = emptyList()
|
@Json(name = "public_keys") val publicKeys: List<PublicKeys>?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
object RoomType {
|
||||||
|
|
||||||
|
const val SPACE = "org.matrix.msc1772.space" // "m.space"
|
||||||
|
// const val MESSAGING = "org.matrix.msc1840.messaging"
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
data class SpaceChildInfo(
|
||||||
|
val childRoomId: String,
|
||||||
|
// We might not know this child at all,
|
||||||
|
// i.e we just know it exists but no info on type/name/etc..
|
||||||
|
val isKnown: Boolean,
|
||||||
|
val roomType: String?,
|
||||||
|
val name: String?,
|
||||||
|
val topic: String?,
|
||||||
|
val avatarUrl: String?,
|
||||||
|
val order: String?,
|
||||||
|
val activeMemberCount: Int?,
|
||||||
|
val autoJoin: Boolean,
|
||||||
|
val viaServers: List<String>,
|
||||||
|
val parentRoomId: String?
|
||||||
|
)
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,14 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.grouplist
|
package org.matrix.android.sdk.api.session.room.model
|
||||||
|
|
||||||
import com.airbnb.mvrx.Async
|
data class SpaceParentInfo(
|
||||||
import com.airbnb.mvrx.MvRxState
|
val parentId: String?,
|
||||||
import com.airbnb.mvrx.Uninitialized
|
val roomSummary: RoomSummary?,
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
val canonical: Boolean?,
|
||||||
|
val viaServers: List<String>
|
||||||
data class GroupListViewState(
|
)
|
||||||
val asyncGroups: Async<List<GroupSummary>> = Uninitialized,
|
|
||||||
val selectedGroup: GroupSummary? = null
|
|
||||||
) : MvRxState
|
|
|
@ -18,13 +18,15 @@ package org.matrix.android.sdk.api.session.room.model.create
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
|
||||||
// TODO Give a way to include other initial states
|
// TODO Give a way to include other initial states
|
||||||
class CreateRoomParams {
|
open class CreateRoomParams {
|
||||||
/**
|
/**
|
||||||
* A public visibility indicates that the room will be shown in the published room list.
|
* A public visibility indicates that the room will be shown in the published room list.
|
||||||
* A private visibility will hide the room from the published room list.
|
* A private visibility will hide the room from the published room list.
|
||||||
|
@ -68,6 +70,11 @@ class CreateRoomParams {
|
||||||
*/
|
*/
|
||||||
val invite3pids = mutableListOf<ThreePid>()
|
val invite3pids = mutableListOf<ThreePid>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial Guest Access
|
||||||
|
*/
|
||||||
|
var guestAccess: GuestAccess? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
|
* If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
|
||||||
* the encryption will be enabled on the created room
|
* the encryption will be enabled on the created room
|
||||||
|
@ -111,6 +118,17 @@ class CreateRoomParams {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var roomType: String? = null // RoomType.MESSAGING
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (value != null) {
|
||||||
|
creationContent[CREATION_CONTENT_KEY_ROOM_TYPE] = value
|
||||||
|
} else {
|
||||||
|
// This is the default value, we remove the field
|
||||||
|
creationContent.remove(CREATION_CONTENT_KEY_ROOM_TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The power level content to override in the default power level event
|
* The power level content to override in the default power level event
|
||||||
*/
|
*/
|
||||||
|
@ -136,7 +154,12 @@ class CreateRoomParams {
|
||||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var roomVersion: String? = null
|
||||||
|
|
||||||
|
var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
|
||||||
|
private const val CREATION_CONTENT_KEY_ROOM_TYPE = "org.matrix.msc1772.type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,5 +26,7 @@ import com.squareup.moshi.JsonClass
|
||||||
data class RoomCreateContent(
|
data class RoomCreateContent(
|
||||||
@Json(name = "creator") val creator: String? = null,
|
@Json(name = "creator") val creator: String? = null,
|
||||||
@Json(name = "room_version") val roomVersion: String? = null,
|
@Json(name = "room_version") val roomVersion: String? = null,
|
||||||
@Json(name = "predecessor") val predecessor: Predecessor? = null
|
@Json(name = "predecessor") val predecessor: Predecessor? = null,
|
||||||
|
// Defines the room type, see #RoomType (user extensible)
|
||||||
|
@Json(name = "org.matrix.msc1772.type") val type: String? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.peeking
|
package org.matrix.android.sdk.api.session.room.peeking
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
sealed class PeekResult {
|
sealed class PeekResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
@ -24,7 +26,9 @@ sealed class PeekResult {
|
||||||
val topic: String?,
|
val topic: String?,
|
||||||
val avatarUrl: String?,
|
val avatarUrl: String?,
|
||||||
val numJoinedMembers: Int?,
|
val numJoinedMembers: Int?,
|
||||||
val viaServers: List<String>
|
val roomType: String?,
|
||||||
|
val viaServers: List<String>,
|
||||||
|
val someMembers: List<MatrixItem.UserItem>?
|
||||||
) : PeekResult()
|
) : PeekResult()
|
||||||
|
|
||||||
data class PeekingNotAllowed(
|
data class PeekingNotAllowed(
|
||||||
|
@ -34,4 +38,6 @@ sealed class PeekResult {
|
||||||
) : PeekResult()
|
) : PeekResult()
|
||||||
|
|
||||||
object UnknownAlias : PeekResult()
|
object UnknownAlias : PeekResult()
|
||||||
|
|
||||||
|
fun isSuccess() = this is Success
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,13 @@
|
||||||
package org.matrix.android.sdk.api.session.room.powerlevels
|
package org.matrix.android.sdk.api.session.room.powerlevels
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.banOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.inviteOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.kickOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.redactOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an helper around PowerLevelsContent.
|
* This class is an helper around PowerLevelsContent.
|
||||||
|
@ -31,9 +38,9 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
* @return the power level
|
* @return the power level
|
||||||
*/
|
*/
|
||||||
fun getUserPowerLevelValue(userId: String): Int {
|
fun getUserPowerLevelValue(userId: String): Int {
|
||||||
return powerLevelsContent.users.getOrElse(userId) {
|
return powerLevelsContent.users
|
||||||
powerLevelsContent.usersDefault
|
?.get(userId)
|
||||||
}
|
?: powerLevelsContent.usersDefaultOrDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +52,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
fun getUserRole(userId: String): Role {
|
fun getUserRole(userId: String): Role {
|
||||||
val value = getUserPowerLevelValue(userId)
|
val value = getUserPowerLevelValue(userId)
|
||||||
// I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web
|
// I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web
|
||||||
return Role.fromValue(value, powerLevelsContent.eventsDefault)
|
return Role.fromValue(value, powerLevelsContent.eventsDefaultOrDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,11 +66,11 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
|
fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
|
||||||
return if (userId.isNotEmpty()) {
|
return if (userId.isNotEmpty()) {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
val minimumPowerLevel = powerLevelsContent.events?.get(eventType)
|
||||||
?: if (isState) {
|
?: if (isState) {
|
||||||
powerLevelsContent.stateDefault
|
powerLevelsContent.stateDefaultOrDefault()
|
||||||
} else {
|
} else {
|
||||||
powerLevelsContent.eventsDefault
|
powerLevelsContent.eventsDefaultOrDefault()
|
||||||
}
|
}
|
||||||
powerLevel >= minimumPowerLevel
|
powerLevel >= minimumPowerLevel
|
||||||
} else false
|
} else false
|
||||||
|
@ -76,7 +83,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
*/
|
*/
|
||||||
fun isUserAbleToInvite(userId: String): Boolean {
|
fun isUserAbleToInvite(userId: String): Boolean {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
return powerLevel >= powerLevelsContent.invite
|
return powerLevel >= powerLevelsContent.inviteOrDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +93,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
*/
|
*/
|
||||||
fun isUserAbleToBan(userId: String): Boolean {
|
fun isUserAbleToBan(userId: String): Boolean {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
return powerLevel >= powerLevelsContent.ban
|
return powerLevel >= powerLevelsContent.banOrDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,7 +103,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
*/
|
*/
|
||||||
fun isUserAbleToKick(userId: String): Boolean {
|
fun isUserAbleToKick(userId: String): Boolean {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
return powerLevel >= powerLevelsContent.kick
|
return powerLevel >= powerLevelsContent.kickOrDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,6 +113,6 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
||||||
*/
|
*/
|
||||||
fun isUserAbleToRedact(userId: String): Boolean {
|
fun isUserAbleToRedact(userId: String): Boolean {
|
||||||
val powerLevel = getUserPowerLevelValue(userId)
|
val powerLevel = getUserPowerLevelValue(userId)
|
||||||
return powerLevel >= powerLevelsContent.redact
|
return powerLevel >= powerLevelsContent.redactOrDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ data class RoomAggregateNotificationCount(
|
||||||
val unreadCount: Int,
|
val unreadCount: Int,
|
||||||
val markedUnreadCount: Int
|
val markedUnreadCount: Int
|
||||||
) {
|
) {
|
||||||
val totalCount = notificationCount + highlightCount + markedUnreadCount
|
val totalCount = notificationCount + markedUnreadCount
|
||||||
val isHighlight = highlightCount > 0
|
val isHighlight = highlightCount > 0
|
||||||
val markedUnread = markedUnreadCount > 0
|
val markedUnread = markedUnreadCount > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.space
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
|
||||||
|
class CreateSpaceParams : CreateRoomParams() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space
|
||||||
|
roomType = RoomType.SPACE
|
||||||
|
|
||||||
|
// Space-rooms should be created with a power level for events_default of 100,
|
||||||
|
// to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space.
|
||||||
|
powerLevelContentOverride = PowerLevelsContent(
|
||||||
|
eventsDefault = 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.space
|
||||||
|
|
||||||
|
sealed class JoinSpaceResult {
|
||||||
|
object Success : JoinSpaceResult()
|
||||||
|
data class Fail(val error: Throwable) : JoinSpaceResult()
|
||||||
|
|
||||||
|
/** Success fully joined the space, but failed to join all or some of it's rooms */
|
||||||
|
data class PartialSuccess(val failedRooms: Map<String, Throwable>) : JoinSpaceResult()
|
||||||
|
|
||||||
|
fun isSuccess() = this is Success || this is PartialSuccess
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.space
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
interface Space {
|
||||||
|
|
||||||
|
fun asRoom(): Room
|
||||||
|
|
||||||
|
val spaceId: String
|
||||||
|
|
||||||
|
suspend fun leave(reason: String? = null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A current snapshot of [RoomSummary] associated with the space
|
||||||
|
*/
|
||||||
|
fun spaceSummary(): RoomSummary?
|
||||||
|
|
||||||
|
suspend fun addChildren(roomId: String,
|
||||||
|
viaServers: List<String>,
|
||||||
|
order: String?,
|
||||||
|
autoJoin: Boolean = false,
|
||||||
|
suggested: Boolean? = false)
|
||||||
|
|
||||||
|
suspend fun removeChildren(roomId: String)
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun setChildrenOrder(roomId: String, order: String?)
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
|
||||||
|
|
||||||
|
// fun getChildren() : List<IRoomSummary>
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.space
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||||
|
|
||||||
|
typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
|
||||||
|
|
||||||
|
interface SpaceService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a space asynchronously
|
||||||
|
* @return the spaceId of the created space
|
||||||
|
*/
|
||||||
|
suspend fun createSpace(params: CreateSpaceParams): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just a shortcut for space creation for ease of use
|
||||||
|
*/
|
||||||
|
suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a space from a roomId
|
||||||
|
* @param spaceId the roomId to look for.
|
||||||
|
* @return a space with spaceId or null if room type is not space
|
||||||
|
*/
|
||||||
|
fun getSpace(spaceId: String): Space?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to resolve (peek) rooms and subspace in this space.
|
||||||
|
* Use this call get preview of children of this space, particularly useful to get a
|
||||||
|
* preview of rooms that you did not join yet.
|
||||||
|
*/
|
||||||
|
suspend fun peekSpace(spaceId: String): SpacePeekResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's information of a space by querying the server
|
||||||
|
*/
|
||||||
|
suspend fun querySpaceChildren(spaceId: String,
|
||||||
|
suggestedOnly: Boolean? = null,
|
||||||
|
autoJoinedOnly: Boolean? = null): Pair<RoomSummary, List<SpaceChildInfo>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||||
|
* @return the [LiveData] of List[SpaceSummary]
|
||||||
|
*/
|
||||||
|
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
|
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary>
|
||||||
|
|
||||||
|
suspend fun joinSpace(spaceIdOrAlias: String,
|
||||||
|
reason: String? = null,
|
||||||
|
viaServers: List<String> = emptyList()): JoinSpaceResult
|
||||||
|
|
||||||
|
suspend fun rejectInvite(spaceId: String, reason: String?)
|
||||||
|
|
||||||
|
// fun getSpaceParentsOfRoom(roomId: String) : List<SpaceSummary>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let this room declare that it has a parent.
|
||||||
|
* @param canonical true if it should be the main parent of this room
|
||||||
|
* In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
|
||||||
|
* if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
|
||||||
|
*/
|
||||||
|
suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>)
|
||||||
|
|
||||||
|
fun getRootSpaceSummaries(): List<RoomSummary>
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.space.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "content": {
|
||||||
|
* "via": ["example.com"],
|
||||||
|
* "order": "abcd",
|
||||||
|
* "default": true
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class SpaceChildContent(
|
||||||
|
/**
|
||||||
|
* Key which gives a list of candidate servers that can be used to join the room
|
||||||
|
* Children where via is not present are ignored.
|
||||||
|
*/
|
||||||
|
@Json(name = "via") val via: List<String>? = null,
|
||||||
|
/**
|
||||||
|
* The order key is a string which is used to provide a default ordering of siblings in the room list.
|
||||||
|
* (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last.
|
||||||
|
* orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
|
||||||
|
* or consist of more than 50 characters, are forbidden and should be ignored if received.)
|
||||||
|
*/
|
||||||
|
@Json(name = "order") val order: String? = null,
|
||||||
|
/**
|
||||||
|
* The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should
|
||||||
|
* be automatically joined by members of that space.
|
||||||
|
* (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.)
|
||||||
|
*/
|
||||||
|
@Json(name = "auto_join") val autoJoin: Boolean? = false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `suggested` is set to `true`, that indicates that the child should be advertised to
|
||||||
|
* members of the space by the client. This could be done by showing them eagerly
|
||||||
|
* in the room list. This is should be ignored if `auto_join` is set to `true`.
|
||||||
|
*/
|
||||||
|
@Json(name = "suggested") val suggested: Boolean? = false
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
|
||||||
|
* or consist of more than 50 characters, are forbidden and should be ignored if received.)
|
||||||
|
*/
|
||||||
|
fun validOrder(): String? {
|
||||||
|
return order
|
||||||
|
?.takeIf { it.length <= 50 }
|
||||||
|
?.takeIf { ORDER_VALID_CHAR_REGEX.matches(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ORDER_VALID_CHAR_REGEX = "[ -~]+".toRegex()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.space.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rooms can claim parents via the m.space.parent state event.
|
||||||
|
* {
|
||||||
|
* "type": "m.space.parent",
|
||||||
|
* "state_key": "!space:example.com",
|
||||||
|
* "content": {
|
||||||
|
* "via": ["example.com"],
|
||||||
|
* "canonical": true,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class SpaceParentContent(
|
||||||
|
/**
|
||||||
|
* Key which gives a list of candidate servers that can be used to join the parent.
|
||||||
|
* Parents where via is not present are ignored.
|
||||||
|
*/
|
||||||
|
@Json(name = "via") val via: List<String>? = null,
|
||||||
|
/**
|
||||||
|
* Canonical determines whether this is the main parent for the space.
|
||||||
|
* When a user joins a room with a canonical parent, clients may switch to view the room
|
||||||
|
* in the context of that space, peeking into it in order to find other rooms and group them together.
|
||||||
|
* In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
|
||||||
|
* if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
|
||||||
|
*/
|
||||||
|
@Json(name = "canonical") val canonical: Boolean? = false
|
||||||
|
)
|
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.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.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
|
@ -157,3 +158,5 @@ fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAl
|
||||||
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
|
|
||||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||||
|
|
||||||
|
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name, avatarUrl)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
object MimeTypes {
|
object MimeTypes {
|
||||||
const val Any: String = "*/*"
|
const val Any: String = "*/*"
|
||||||
const val OctetStream = "application/octet-stream"
|
const val OctetStream = "application/octet-stream"
|
||||||
|
const val Apk = "application/vnd.android.package-archive"
|
||||||
|
|
||||||
const val Images = "image/*"
|
const val Images = "image/*"
|
||||||
|
|
||||||
|
|
|
@ -144,16 +144,14 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
return result.fold(
|
return result.fold(
|
||||||
{
|
{
|
||||||
if (it is LoginFlowResult.Success) {
|
// The homeserver exists and up to date, keep the config
|
||||||
// The homeserver exists and up to date, keep the config
|
// Homeserver url may have been changed, if it was a Riot url
|
||||||
// Homeserver url may have been changed, if it was a Riot url
|
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||||
homeServerUri = Uri.parse(it.homeServerUrl)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||||
}
|
|
||||||
it
|
it
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
val loginFlowResponse = executeRequest(null) {
|
val loginFlowResponse = executeRequest(null) {
|
||||||
authAPI.getLoginFlows()
|
authAPI.getLoginFlows()
|
||||||
}
|
}
|
||||||
return LoginFlowResult.Success(
|
return LoginFlowResult(
|
||||||
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||||
loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
|
||||||
homeServerUrl,
|
homeServerUrl = homeServerUrl,
|
||||||
!versions.isSupportedBySdk()
|
isOutdatedHomeserver = !versions.isSupportedBySdk()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,8 @@ internal class DefaultRegistrationWizard(
|
||||||
return performRegistrationRequest(params)
|
return performRegistrationRequest(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createAccount(userName: String,
|
override suspend fun createAccount(userName: String?,
|
||||||
password: String,
|
password: String?,
|
||||||
initialDeviceDisplayName: String?): RegistrationResult {
|
initialDeviceDisplayName: String?): RegistrationResult {
|
||||||
val params = RegistrationParams(
|
val params = RegistrationParams(
|
||||||
username = userName,
|
username = userName,
|
||||||
|
|
|
@ -44,7 +44,7 @@ data class CryptoDeviceInfo(
|
||||||
*/
|
*/
|
||||||
fun fingerprint(): String? {
|
fun fingerprint(): String? {
|
||||||
return keys
|
return keys
|
||||||
?.takeIf { !deviceId.isBlank() }
|
?.takeIf { deviceId.isNotBlank() }
|
||||||
?.get("ed25519:$deviceId")
|
?.get("ed25519:$deviceId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ data class CryptoDeviceInfo(
|
||||||
*/
|
*/
|
||||||
fun identityKey(): String? {
|
fun identityKey(): String? {
|
||||||
return keys
|
return keys
|
||||||
?.takeIf { !deviceId.isBlank() }
|
?.takeIf { deviceId.isNotBlank() }
|
||||||
?.get("curve25519:$deviceId")
|
?.get("curve25519:$deviceId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ data class MXDeviceInfo(
|
||||||
*/
|
*/
|
||||||
fun fingerprint(): String? {
|
fun fingerprint(): String? {
|
||||||
return keys
|
return keys
|
||||||
?.takeIf { !deviceId.isBlank() }
|
?.takeIf { deviceId.isNotBlank() }
|
||||||
?.get("ed25519:$deviceId")
|
?.get("ed25519:$deviceId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ data class MXDeviceInfo(
|
||||||
*/
|
*/
|
||||||
fun identityKey(): String? {
|
fun identityKey(): String? {
|
||||||
return keys
|
return keys
|
||||||
?.takeIf { !deviceId.isBlank() }
|
?.takeIf { deviceId.isNotBlank() }
|
||||||
?.get("curve25519:$deviceId")
|
?.get("curve25519:$deviceId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
|
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||||
|
@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -47,7 +48,7 @@ private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300
|
||||||
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
|
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
||||||
awaitTransaction(realmConfiguration) { realm ->
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.util.createBackgroundHandler
|
import org.matrix.android.sdk.internal.util.createBackgroundHandler
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmChangeListener
|
import io.realm.RealmChangeListener
|
||||||
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.cancelChildren
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
||||||
private val backgroundRealm = AtomicReference<Realm>()
|
private val backgroundRealm = AtomicReference<Realm>()
|
||||||
private lateinit var results: AtomicReference<RealmResults<T>>
|
private lateinit var results: AtomicReference<RealmResults<T>>
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
val realm = Realm.getInstance(realmConfiguration)
|
||||||
|
@ -58,7 +59,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStopped() {
|
override fun onSessionStopped(session: Session) {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
results.getAndSet(null).removeAllChangeListeners()
|
results.getAndSet(null).removeAllChangeListeners()
|
||||||
|
@ -70,7 +71,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClearCache() {
|
override fun onClearCache(session: Session) {
|
||||||
observerScope.coroutineContext.cancelChildren()
|
observerScope.coroutineContext.cancelChildren()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,9 @@ import android.os.Looper
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.concurrent.getOrSet
|
import kotlin.concurrent.getOrSet
|
||||||
|
@ -44,14 +45,14 @@ internal class RealmSessionProvider @Inject constructor(@SessionDatabase private
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
realmThreadLocal.getOrSet {
|
realmThreadLocal.getOrSet {
|
||||||
Realm.getInstance(monarchy.realmConfiguration)
|
Realm.getInstance(monarchy.realmConfiguration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
override fun onSessionStopped() {
|
override fun onSessionStopped(session: Session) {
|
||||||
realmThreadLocal.get()?.close()
|
realmThreadLocal.get()?.close()
|
||||||
realmThreadLocal.remove()
|
realmThreadLocal.remove()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,10 @@ package org.matrix.android.sdk.internal.database
|
||||||
import io.realm.DynamicRealm
|
import io.realm.DynamicRealm
|
||||||
import io.realm.FieldAttribute
|
import io.realm.FieldAttribute
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
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.internal.database.model.CurrentStateEventEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
|
@ -31,6 +34,9 @@ import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||||
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.model.RoomTagEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -39,10 +45,10 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
companion object {
|
companion object {
|
||||||
// SC-specific DB changes on top of Element
|
// SC-specific DB changes on top of Element
|
||||||
// 1: added markedUnread field
|
// 1: added markedUnread field
|
||||||
const val SESSION_STORE_SCHEMA_SC_VERSION = 1L
|
const val SESSION_STORE_SCHEMA_SC_VERSION = 2L
|
||||||
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
|
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
|
||||||
|
|
||||||
const val SESSION_STORE_SCHEMA_VERSION = 9L +
|
const val SESSION_STORE_SCHEMA_VERSION = 10L +
|
||||||
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +67,10 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
if (oldVersion <= 6) migrateTo7(realm)
|
if (oldVersion <= 6) migrateTo7(realm)
|
||||||
if (oldVersion <= 7) migrateTo8(realm)
|
if (oldVersion <= 7) migrateTo8(realm)
|
||||||
if (oldVersion <= 8) migrateTo9(realm)
|
if (oldVersion <= 8) migrateTo9(realm)
|
||||||
|
if (oldVersion <= 9) migrateTo10(realm)
|
||||||
|
|
||||||
if (oldScVersion <= 0) migrateToSc1(realm)
|
if (oldScVersion <= 0) migrateToSc1(realm)
|
||||||
|
if (oldScVersion <= 1) migrateToSc2(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SC Version 1L added markedUnread
|
// SC Version 1L added markedUnread
|
||||||
|
@ -72,6 +80,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
?.addField(RoomSummaryEntityFields.MARKED_UNREAD, Boolean::class.java)
|
?.addField(RoomSummaryEntityFields.MARKED_UNREAD, Boolean::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SC Version 2L added unreadCount
|
||||||
|
private fun migrateToSc2(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step SC 1 -> 2")
|
||||||
|
realm.schema.get("RoomSummaryEntity")
|
||||||
|
?.addField(RoomSummaryEntityFields.UNREAD_COUNT, Int::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -194,7 +209,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||||
|
|
||||||
?.transform { obj ->
|
?.transform { obj ->
|
||||||
|
|
||||||
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
||||||
}
|
}
|
||||||
|
@ -214,4 +228,44 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun migrateTo10(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 9 -> 10")
|
||||||
|
realm.schema.create("SpaceChildSummaryEntity")
|
||||||
|
?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
|
||||||
|
?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
|
||||||
|
?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
|
||||||
|
?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
|
||||||
|
?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||||
|
?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||||
|
|
||||||
|
realm.schema.create("SpaceParentSummaryEntity")
|
||||||
|
?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
|
||||||
|
?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
|
||||||
|
?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
|
||||||
|
?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
|
||||||
|
?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
|
||||||
|
|
||||||
|
val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
|
||||||
|
realm.schema.get("RoomSummaryEntity")
|
||||||
|
?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
|
||||||
|
?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
|
||||||
|
?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
|
||||||
|
?.transform { obj ->
|
||||||
|
|
||||||
|
val creationEvent = realm.where("CurrentStateEventEntity")
|
||||||
|
.equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
|
||||||
|
.equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
|
||||||
|
.findFirst()
|
||||||
|
|
||||||
|
val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
|
||||||
|
?.getString(EventEntityFields.CONTENT)?.let {
|
||||||
|
creationContentAdapter.fromJson(it)?.type
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
|
||||||
|
}
|
||||||
|
?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
|
||||||
|
?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.matrix.android.sdk.internal.database.mapper
|
package org.matrix.android.sdk.internal.database.mapper
|
||||||
|
|
||||||
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.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
|
||||||
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.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
||||||
|
@ -49,6 +51,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||||
topic = roomSummaryEntity.topic ?: "",
|
topic = roomSummaryEntity.topic ?: "",
|
||||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||||
isDirect = roomSummaryEntity.isDirect,
|
isDirect = roomSummaryEntity.isDirect,
|
||||||
|
directUserId = roomSummaryEntity.directUserId,
|
||||||
latestPreviewableEvent = latestEvent,
|
latestPreviewableEvent = latestEvent,
|
||||||
latestPreviewableContentEvent = latestContentEvent,
|
latestPreviewableContentEvent = latestContentEvent,
|
||||||
latestPreviewableOriginalContentEvent = latestOriginalContentEvent,
|
latestPreviewableOriginalContentEvent = latestOriginalContentEvent,
|
||||||
|
@ -74,7 +77,32 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||||
inviterId = roomSummaryEntity.inviterId,
|
inviterId = roomSummaryEntity.inviterId,
|
||||||
hasFailedSending = roomSummaryEntity.hasFailedSending
|
hasFailedSending = roomSummaryEntity.hasFailedSending,
|
||||||
|
roomType = roomSummaryEntity.roomType,
|
||||||
|
spaceParents = roomSummaryEntity.parents.map { relationInfoEntity ->
|
||||||
|
SpaceParentInfo(
|
||||||
|
parentId = relationInfoEntity.parentRoomId,
|
||||||
|
roomSummary = relationInfoEntity.parentSummaryEntity?.let { map(it) },
|
||||||
|
canonical = relationInfoEntity.canonical ?: false,
|
||||||
|
viaServers = relationInfoEntity.viaServers.toList()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
spaceChildren = roomSummaryEntity.children.map {
|
||||||
|
SpaceChildInfo(
|
||||||
|
childRoomId = it.childRoomId ?: "",
|
||||||
|
isKnown = it.childSummaryEntity != null,
|
||||||
|
roomType = it.childSummaryEntity?.roomType,
|
||||||
|
name = it.childSummaryEntity?.name,
|
||||||
|
topic = it.childSummaryEntity?.topic,
|
||||||
|
avatarUrl = it.childSummaryEntity?.avatarUrl,
|
||||||
|
activeMemberCount = it.childSummaryEntity?.joinedMembersCount,
|
||||||
|
order = it.order,
|
||||||
|
autoJoin = it.autoJoin ?: false,
|
||||||
|
viaServers = it.viaServers.toList(),
|
||||||
|
parentRoomId = roomSummaryEntity.roomId
|
||||||
|
)
|
||||||
|
},
|
||||||
|
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,5 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||||
set(value) {
|
set(value) {
|
||||||
membersLoadStatusStr = value.name
|
membersLoadStatusStr = value.name
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,10 @@ import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
|
|
||||||
internal open class RoomSummaryEntity(
|
internal open class RoomSummaryEntity(
|
||||||
@PrimaryKey var roomId: String = ""
|
@PrimaryKey var roomId: String = "",
|
||||||
|
var roomType: String? = null,
|
||||||
|
var parents: RealmList<SpaceParentSummaryEntity> = RealmList(),
|
||||||
|
var children: RealmList<SpaceChildSummaryEntity> = RealmList()
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
var displayName: String? = ""
|
var displayName: String? = ""
|
||||||
|
@ -103,6 +106,17 @@ internal open class RoomSummaryEntity(
|
||||||
if (value != field) field = value
|
if (value != field) field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unreadCount: Int = 0
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
get() {
|
||||||
|
if (field == 0 && hasUnreadOriginalContentMessages) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
var readMarkerId: String? = null
|
var readMarkerId: String? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value != field) field = value
|
if (value != field) field = value
|
||||||
|
@ -229,6 +243,16 @@ internal open class RoomSummaryEntity(
|
||||||
if (value != field) field = value
|
if (value != field) field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flattenParentIds: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupIds: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (value != field) field = value
|
||||||
|
}
|
||||||
|
|
||||||
@Index
|
@Index
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
|
||||||
|
@ -269,6 +293,5 @@ internal open class RoomSummaryEntity(
|
||||||
roomEncryptionTrustLevelStr = value?.name
|
roomEncryptionTrustLevelStr = value?.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ import io.realm.annotations.RealmModule
|
||||||
CurrentStateEventEntity::class,
|
CurrentStateEventEntity::class,
|
||||||
UserAccountDataEntity::class,
|
UserAccountDataEntity::class,
|
||||||
ScalarTokenEntity::class,
|
ScalarTokenEntity::class,
|
||||||
WellknownIntegrationManagerConfigEntity::class
|
WellknownIntegrationManagerConfigEntity::class,
|
||||||
|
SpaceChildSummaryEntity::class,
|
||||||
|
SpaceParentSummaryEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates room summary with space related information.
|
||||||
|
*/
|
||||||
|
internal open class SpaceChildSummaryEntity(
|
||||||
|
// var isSpace: Boolean = false,
|
||||||
|
|
||||||
|
var order: String? = null,
|
||||||
|
|
||||||
|
var autoJoin: Boolean? = null,
|
||||||
|
|
||||||
|
var childRoomId: String? = null,
|
||||||
|
// Link to the actual space summary if it is known locally
|
||||||
|
var childSummaryEntity: RoomSummaryEntity? = null,
|
||||||
|
|
||||||
|
var viaServers: RealmList<String> = RealmList()
|
||||||
|
// var owner: RoomSummaryEntity? = null,
|
||||||
|
|
||||||
|
// var level: Int = 0
|
||||||
|
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates room summary with space related information.
|
||||||
|
*/
|
||||||
|
internal open class SpaceParentSummaryEntity(
|
||||||
|
/**
|
||||||
|
* Determines whether this is the main parent for the space
|
||||||
|
* When a user joins a room with a canonical parent, clients may switch to view the room in the context of that space,
|
||||||
|
* peeking into it in order to find other rooms and group them together.
|
||||||
|
* In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
|
||||||
|
* if multiple are present the client should select the one with the lowest room ID,
|
||||||
|
* as determined via a lexicographic utf-8 ordering.
|
||||||
|
*/
|
||||||
|
var canonical: Boolean? = null,
|
||||||
|
|
||||||
|
var parentRoomId: String? = null,
|
||||||
|
// Link to the actual space summary if it is known locally
|
||||||
|
var parentSummaryEntity: RoomSummaryEntity? = null,
|
||||||
|
|
||||||
|
var viaServers: RealmList<String> = RealmList()
|
||||||
|
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -88,8 +88,8 @@ internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErr
|
||||||
throw when (exception) {
|
throw when (exception) {
|
||||||
is IOException -> Failure.NetworkConnection(exception)
|
is IOException -> Failure.NetworkConnection(exception)
|
||||||
is Failure.ServerError,
|
is Failure.ServerError,
|
||||||
is Failure.OtherServerError -> exception
|
is Failure.OtherServerError,
|
||||||
is CancellationException -> Failure.Cancelled(exception)
|
is CancellationException -> exception
|
||||||
else -> Failure.Unknown(exception)
|
else -> Failure.Unknown(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.query
|
||||||
|
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.Sort
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
|
|
||||||
|
internal fun RealmQuery<RoomSummaryEntity>.process(sortOrder: RoomSortOrder): RealmQuery<RoomSummaryEntity> {
|
||||||
|
when (sortOrder) {
|
||||||
|
RoomSortOrder.NAME -> {
|
||||||
|
sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
|
||||||
|
}
|
||||||
|
RoomSortOrder.ACTIVITY -> {
|
||||||
|
sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
RoomSortOrder.NONE -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
|
@ -16,10 +16,10 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.query
|
package org.matrix.android.sdk.internal.query
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
|
||||||
import io.realm.Case
|
import io.realm.Case
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||||
|
|
|
@ -219,7 +219,7 @@ internal class DefaultFileService @Inject constructor(
|
||||||
fileName: String,
|
fileName: String,
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?): Boolean {
|
elementToDecrypt: ElementToDecrypt?): Boolean {
|
||||||
return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE
|
return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) is FileService.FileState.InCache
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class CachedFiles(
|
internal data class CachedFiles(
|
||||||
|
@ -256,12 +256,17 @@ internal class DefaultFileService @Inject constructor(
|
||||||
fileName: String,
|
fileName: String,
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
|
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
|
||||||
mxcUrl ?: return FileService.FileState.UNKNOWN
|
mxcUrl ?: return FileService.FileState.Unknown
|
||||||
if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE
|
val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null)
|
||||||
|
if (files.file.exists()) {
|
||||||
|
return FileService.FileState.InCache(
|
||||||
|
decryptedFileInCache = files.getClearFile().exists()
|
||||||
|
)
|
||||||
|
}
|
||||||
val isDownloading = synchronized(ongoing) {
|
val isDownloading = synchronized(ongoing) {
|
||||||
ongoing[mxcUrl] != null
|
ongoing[mxcUrl] != null
|
||||||
}
|
}
|
||||||
return if (isDownloading) FileService.FileState.DOWNLOADING else FileService.FileState.UNKNOWN
|
return if (isDownloading) FileService.FileState.Downloading else FileService.FileState.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,13 +19,15 @@ package org.matrix.android.sdk.internal.session
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import org.matrix.android.sdk.api.federation.FederationService
|
import org.matrix.android.sdk.api.federation.FederationService
|
||||||
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
||||||
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.api.session.account.AccountService
|
import org.matrix.android.sdk.api.session.account.AccountService
|
||||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
||||||
import org.matrix.android.sdk.api.session.cache.CacheService
|
import org.matrix.android.sdk.api.session.cache.CacheService
|
||||||
|
@ -38,6 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.group.GroupService
|
import org.matrix.android.sdk.api.session.group.GroupService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
|
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
import org.matrix.android.sdk.api.session.media.MediaService
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
|
@ -49,6 +52,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||||
import org.matrix.android.sdk.api.session.sync.FilterService
|
import org.matrix.android.sdk.api.session.sync.FilterService
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
|
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
|
||||||
|
@ -120,6 +124,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val integrationManagerService: IntegrationManagerService,
|
private val integrationManagerService: IntegrationManagerService,
|
||||||
private val thirdPartyService: Lazy<ThirdPartyService>,
|
private val thirdPartyService: Lazy<ThirdPartyService>,
|
||||||
private val callSignalingService: Lazy<CallSignalingService>,
|
private val callSignalingService: Lazy<CallSignalingService>,
|
||||||
|
private val spaceService: Lazy<SpaceService>,
|
||||||
@UnauthenticatedWithCertificate
|
@UnauthenticatedWithCertificate
|
||||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
||||||
) : Session,
|
) : Session,
|
||||||
|
@ -159,7 +164,12 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = true
|
isOpen = true
|
||||||
cryptoService.get().ensureDevice()
|
cryptoService.get().ensureDevice()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onSessionStarted() }
|
lifecycleObservers.forEach {
|
||||||
|
it.onSessionStarted(this)
|
||||||
|
}
|
||||||
|
sessionListeners.dispatch {
|
||||||
|
it.onSessionStarted(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
globalErrorHandler.listener = this
|
globalErrorHandler.listener = this
|
||||||
}
|
}
|
||||||
|
@ -200,7 +210,10 @@ internal class DefaultSession @Inject constructor(
|
||||||
stopSync()
|
stopSync()
|
||||||
// timelineEventDecryptor.destroy()
|
// timelineEventDecryptor.destroy()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onSessionStopped() }
|
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
||||||
|
sessionListeners.dispatch {
|
||||||
|
it.onSessionStopped(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cryptoService.get().close()
|
cryptoService.get().close()
|
||||||
isOpen = false
|
isOpen = false
|
||||||
|
@ -225,14 +238,23 @@ internal class DefaultSession @Inject constructor(
|
||||||
stopSync()
|
stopSync()
|
||||||
stopAnyBackgroundSync()
|
stopAnyBackgroundSync()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onClearCache() }
|
lifecycleObservers.forEach {
|
||||||
|
it.onClearCache(this)
|
||||||
|
}
|
||||||
|
sessionListeners.dispatch {
|
||||||
|
it.onClearCache(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
cacheService.get().clearCache()
|
||||||
}
|
}
|
||||||
cacheService.get().clearCache()
|
|
||||||
workManagerProvider.cancelAllWorks()
|
workManagerProvider.cancelAllWorks()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGlobalError(globalError: GlobalError) {
|
override fun onGlobalError(globalError: GlobalError) {
|
||||||
sessionListeners.dispatchGlobalError(globalError)
|
sessionListeners.dispatch {
|
||||||
|
it.onGlobalError(this, globalError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentUrlResolver() = contentUrlResolver
|
override fun contentUrlResolver() = contentUrlResolver
|
||||||
|
@ -265,6 +287,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
|
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
|
||||||
|
|
||||||
|
override fun spaceService(): SpaceService = spaceService.get()
|
||||||
|
|
||||||
override fun getOkHttpClient(): OkHttpClient {
|
override fun getOkHttpClient(): OkHttpClient {
|
||||||
return unauthenticatedWithCertificateOkHttpClient.get()
|
return unauthenticatedWithCertificateOkHttpClient.get()
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
||||||
import org.matrix.android.sdk.internal.session.search.SearchModule
|
import org.matrix.android.sdk.internal.session.search.SearchModule
|
||||||
import org.matrix.android.sdk.internal.session.signout.SignOutModule
|
import org.matrix.android.sdk.internal.session.signout.SignOutModule
|
||||||
|
import org.matrix.android.sdk.internal.session.space.SpaceModule
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncModule
|
import org.matrix.android.sdk.internal.session.sync.SyncModule
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||||
|
@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
FederationModule::class,
|
FederationModule::class,
|
||||||
CallModule::class,
|
CallModule::class,
|
||||||
SearchModule::class,
|
SearchModule::class,
|
||||||
ThirdPartyModule::class
|
ThirdPartyModule::class,
|
||||||
|
SpaceModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import javax.inject.Inject
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class SessionCoroutineScopeHolder @Inject constructor(): SessionLifecycleObserver {
|
||||||
|
|
||||||
|
val scope: CoroutineScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
|
override fun onSessionStopped(session: Session) {
|
||||||
|
scope.cancelChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClearCache(session: Session) {
|
||||||
|
scope.cancelChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.cancelChildren() {
|
||||||
|
coroutineContext.cancelChildren(CancellationException("Closing session"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session
|
package org.matrix.android.sdk.internal.session
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -36,10 +35,10 @@ internal class SessionListeners @Inject constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchGlobalError(globalError: GlobalError) {
|
fun dispatch(block: (Session.Listener) -> Unit) {
|
||||||
synchronized(listeners) {
|
synchronized(listeners) {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
it.onGlobalError(globalError)
|
block(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.auth.data.sessionId
|
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
|
||||||
import org.matrix.android.sdk.api.session.events.EventService
|
import org.matrix.android.sdk.api.session.events.EventService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
|
@ -343,6 +344,10 @@ internal abstract class SessionModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
|
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
|
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
||||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
|
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
|
||||||
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
|
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
|
||||||
|
@ -51,6 +51,7 @@ import org.matrix.android.sdk.internal.util.ensureProtocol
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -86,7 +87,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
|
|
||||||
private val listeners = mutableSetOf<IdentityServiceListener>()
|
private val listeners = mutableSetOf<IdentityServiceListener>()
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||||
// Observe the account data change
|
// Observe the account data change
|
||||||
accountDataDataSource
|
accountDataDataSource
|
||||||
|
@ -111,7 +112,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStopped() {
|
override fun onSessionStopped(session: Session) {
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
|
@ -29,7 +30,7 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||||
import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity
|
import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
import org.matrix.android.sdk.internal.extensions.observeNotNull
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||||
|
@ -77,7 +78,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
||||||
currentConfigs.add(defaultConfig)
|
currentConfigs.add(defaultConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||||
observeWellknownConfig()
|
observeWellknownConfig()
|
||||||
accountDataDataSource
|
accountDataDataSource
|
||||||
|
@ -105,7 +106,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStopped() {
|
override fun onSessionStopped(session: Session) {
|
||||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.alias.AliasService
|
||||||
import org.matrix.android.sdk.api.session.room.call.RoomCallService
|
import org.matrix.android.sdk.api.session.room.call.RoomCallService
|
||||||
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.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.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
|
||||||
import org.matrix.android.sdk.api.session.room.read.ReadService
|
import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||||
|
@ -36,11 +37,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||||
import org.matrix.android.sdk.api.session.search.SearchResult
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.search.SearchTask
|
import org.matrix.android.sdk.internal.session.search.SearchTask
|
||||||
|
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -148,4 +151,9 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun asSpace(): Space? {
|
||||||
|
if (roomSummary()?.roomType != RoomType.SPACE) return null
|
||||||
|
return DefaultSpace(this, roomSummaryDataSource)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,19 +20,19 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
|
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.UpdatableFilterLivePageResult
|
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
||||||
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.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.peeking.PeekResult
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
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.asDomain
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
|
@ -50,8 +50,6 @@ import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -67,16 +65,11 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
private val peekRoomTask: PeekRoomTask,
|
private val peekRoomTask: PeekRoomTask,
|
||||||
private val roomGetter: RoomGetter,
|
private val roomGetter: RoomGetter,
|
||||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
|
||||||
private val taskExecutor: TaskExecutor
|
|
||||||
) : RoomService {
|
) : RoomService {
|
||||||
|
|
||||||
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
|
override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
|
||||||
return createRoomTask
|
return createRoomTask.executeRetry(createRoomParams, 3)
|
||||||
.configureWith(createRoomParams) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
|
@ -99,14 +92,14 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
|
||||||
: LiveData<PagedList<RoomSummary>> {
|
: LiveData<PagedList<RoomSummary>> {
|
||||||
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
|
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
|
||||||
: UpdatableFilterLivePageResult {
|
: UpdatableLivePageResult {
|
||||||
return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
|
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams, preferenceProvider: RoomSummary.RoomSummaryPreferenceProvider): RoomAggregateNotificationCount {
|
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams, preferenceProvider: RoomSummary.RoomSummaryPreferenceProvider): RoomAggregateNotificationCount {
|
||||||
|
@ -121,34 +114,20 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
|
return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomDisplayed(roomId: String): Cancelable {
|
override suspend fun onRoomDisplayed(roomId: String) {
|
||||||
return updateBreadcrumbsTask
|
updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
|
||||||
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
|
||||||
return joinRoomTask
|
joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
|
||||||
.configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun markAllAsRead(roomIds: List<String>) {
|
||||||
return markAllRoomsReadTask
|
markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
|
||||||
.configureWith(MarkAllRoomsReadTask.Params(roomIds)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable {
|
override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional<RoomAliasDescription> {
|
||||||
return roomIdByAliasTask
|
return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer))
|
||||||
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteRoomAlias(roomAlias: String) {
|
override suspend fun deleteRoomAlias(roomAlias: String) {
|
||||||
|
@ -179,19 +158,25 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) {
|
override suspend fun getRoomState(roomId: String): List<Event> {
|
||||||
resolveRoomStateTask
|
return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
|
||||||
.configureWith(ResolveRoomStateTask.Params(roomId)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) {
|
override suspend fun peekRoom(roomIdOrAlias: String): PeekResult {
|
||||||
peekRoomTask
|
return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias))
|
||||||
.configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
|
}
|
||||||
this.callback = callback
|
|
||||||
}
|
override fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List<Membership>): List<RoomSummary> {
|
||||||
.executeBy(taskExecutor)
|
if (spaceId == null) {
|
||||||
|
return roomSummaryDataSource.getFlattenOrphanRooms()
|
||||||
|
}
|
||||||
|
return roomSummaryDataSource.getAllRoomSummaryChildOf(spaceId, memberships)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?, memberships: List<Membership>): LiveData<List<RoomSummary>> {
|
||||||
|
if (spaceId == null) {
|
||||||
|
return roomSummaryDataSource.getFlattenOrphanRoomsLive()
|
||||||
|
}
|
||||||
|
return roomSummaryDataSource.getAllRoomSummaryChildOfLive(spaceId, memberships)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
|
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
|
||||||
|
@ -91,6 +92,7 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
|
||||||
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
|
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
|
||||||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
|
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
|
||||||
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
||||||
|
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -137,6 +139,9 @@ internal abstract class RoomModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
|
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SpaceGetter {
|
||||||
|
fun get(spaceId: String): Space?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSpaceGetter @Inject constructor(
|
||||||
|
private val roomGetter: RoomGetter
|
||||||
|
) : SpaceGetter {
|
||||||
|
|
||||||
|
override fun get(spaceId: String): Space? {
|
||||||
|
return roomGetter.getRoom(spaceId)?.asSpace()
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,11 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
|
||||||
@Throws(RoomAliasError::class)
|
@Throws(RoomAliasError::class)
|
||||||
suspend fun check(aliasLocalPart: String?) {
|
suspend fun check(aliasLocalPart: String?) {
|
||||||
if (aliasLocalPart.isNullOrEmpty()) {
|
if (aliasLocalPart.isNullOrEmpty()) {
|
||||||
throw RoomAliasError.AliasEmpty
|
// don't check empty or not provided alias
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (aliasLocalPart.isBlank()) {
|
||||||
|
throw RoomAliasError.AliasIsBlank
|
||||||
}
|
}
|
||||||
// Check alias availability
|
// Check alias availability
|
||||||
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
|
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
|
||||||
|
|
|
@ -111,5 +111,12 @@ internal data class CreateRoomBody(
|
||||||
* The power level content to override in the default power level event
|
* The power level content to override in the default power level event
|
||||||
*/
|
*/
|
||||||
@Json(name = "power_level_content_override")
|
@Json(name = "power_level_content_override")
|
||||||
val powerLevelContentOverride: PowerLevelsContent?
|
val powerLevelContentOverride: PowerLevelsContent?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The room version to set for the room. If not provided, the homeserver is to use its configured default.
|
||||||
|
* If provided, the homeserver will return a 400 error with the errcode M_UNSUPPORTED_ROOM_VERSION if it does not support the room version.
|
||||||
|
*/
|
||||||
|
@Json(name = "room_version")
|
||||||
|
val roomVersion: String?
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,13 +20,19 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.identity.IdentityServiceError
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||||
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.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
|
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
|
||||||
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
|
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
|
||||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||||
import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
|
import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
|
||||||
|
@ -43,6 +49,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val identityStore: IdentityStore,
|
private val identityStore: IdentityStore,
|
||||||
private val fileUploader: FileUploader,
|
private val fileUploader: FileUploader,
|
||||||
|
@UserId
|
||||||
|
private val userId: String,
|
||||||
@AuthenticatedIdentity
|
@AuthenticatedIdentity
|
||||||
private val accessTokenProvider: AccessTokenProvider
|
private val accessTokenProvider: AccessTokenProvider
|
||||||
) {
|
) {
|
||||||
|
@ -68,10 +76,17 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.joinRuleRestricted != null) {
|
||||||
|
params.roomVersion = "org.matrix.msc3083"
|
||||||
|
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
|
||||||
|
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
|
||||||
|
}
|
||||||
val initialStates = listOfNotNull(
|
val initialStates = listOfNotNull(
|
||||||
buildEncryptionWithAlgorithmEvent(params),
|
buildEncryptionWithAlgorithmEvent(params),
|
||||||
buildHistoryVisibilityEvent(params),
|
buildHistoryVisibilityEvent(params),
|
||||||
buildAvatarEvent(params)
|
buildAvatarEvent(params),
|
||||||
|
buildGuestAccess(params),
|
||||||
|
buildJoinRulesRestricted(params)
|
||||||
)
|
)
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
@ -80,13 +95,15 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
roomAliasName = params.roomAliasName,
|
roomAliasName = params.roomAliasName,
|
||||||
name = params.name,
|
name = params.name,
|
||||||
topic = params.topic,
|
topic = params.topic,
|
||||||
invitedUserIds = params.invitedUserIds,
|
invitedUserIds = params.invitedUserIds.filter { it != userId },
|
||||||
invite3pids = invite3pids,
|
invite3pids = invite3pids,
|
||||||
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
|
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
|
||||||
initialStates = initialStates,
|
initialStates = initialStates,
|
||||||
preset = params.preset,
|
preset = params.preset,
|
||||||
isDirect = params.isDirect,
|
isDirect = params.isDirect,
|
||||||
powerLevelContentOverride = params.powerLevelContentOverride
|
powerLevelContentOverride = params.powerLevelContentOverride,
|
||||||
|
roomVersion = params.roomVersion
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +137,31 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildGuestAccess(params: CreateRoomParams): Event? {
|
||||||
|
return params.guestAccess
|
||||||
|
?.let {
|
||||||
|
Event(
|
||||||
|
type = EventType.STATE_ROOM_GUEST_ACCESS,
|
||||||
|
stateKey = "",
|
||||||
|
content = mapOf("guest_access" to it.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
|
||||||
|
return params.joinRuleRestricted
|
||||||
|
?.let { allowList ->
|
||||||
|
Event(
|
||||||
|
type = EventType.STATE_ROOM_JOIN_RULES,
|
||||||
|
stateKey = "",
|
||||||
|
content = RoomJoinRulesContent(
|
||||||
|
_joinRules = RoomJoinRules.RESTRICTED.value,
|
||||||
|
allowList = allowList
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the crypto algorithm to the room creation parameters.
|
* Add the crypto algorithm to the room creation parameters.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw CreateRoomFailure.CreatedWithTimeout
|
throw CreateRoomFailure.CreatedWithTimeout(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||||
|
|
|
@ -23,11 +23,14 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
|
||||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
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.util.MatrixItem
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
||||||
|
@ -100,7 +103,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
||||||
name = publicRepoResult.name,
|
name = publicRepoResult.name,
|
||||||
topic = publicRepoResult.topic,
|
topic = publicRepoResult.topic,
|
||||||
numJoinedMembers = publicRepoResult.numJoinedMembers,
|
numJoinedMembers = publicRepoResult.numJoinedMembers,
|
||||||
viaServers = serverList
|
viaServers = serverList,
|
||||||
|
roomType = null, // would be nice to get that from directory...
|
||||||
|
someMembers = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +130,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
||||||
?.let { it.content?.toModel<RoomCanonicalAliasContent>()?.canonicalAlias }
|
?.let { it.content?.toModel<RoomCanonicalAliasContent>()?.canonicalAlias }
|
||||||
|
|
||||||
// not sure if it's the right way to do that :/
|
// not sure if it's the right way to do that :/
|
||||||
val memberCount = stateEvents
|
val membersEvent = stateEvents
|
||||||
.filter { it.type == EventType.STATE_ROOM_MEMBER && it.stateKey?.isNotEmpty() == true }
|
.filter { it.type == EventType.STATE_ROOM_MEMBER && it.stateKey?.isNotEmpty() == true }
|
||||||
|
|
||||||
|
val memberCount = membersEvent
|
||||||
.distinctBy { it.stateKey }
|
.distinctBy { it.stateKey }
|
||||||
.count()
|
.count()
|
||||||
|
|
||||||
|
val someMembers = membersEvent.mapNotNull { ev ->
|
||||||
|
ev.content?.toModel<RoomMemberContent>()?.let {
|
||||||
|
MatrixItem.UserItem(ev.stateKey ?: "", it.displayName, it.avatarUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val roomType = stateEvents
|
||||||
|
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
|
||||||
|
?.content
|
||||||
|
?.toModel<RoomCreateContent>()
|
||||||
|
?.type
|
||||||
|
|
||||||
return PeekResult.Success(
|
return PeekResult.Success(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
alias = alias,
|
alias = alias,
|
||||||
|
@ -137,7 +156,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
||||||
name = name,
|
name = name,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
numJoinedMembers = memberCount,
|
numJoinedMembers = memberCount,
|
||||||
viaServers = serverList
|
roomType = roomType,
|
||||||
|
viaServers = serverList,
|
||||||
|
someMembers = someMembers
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Would be M_FORBIDDEN if cannot peek :/
|
// Would be M_FORBIDDEN if cannot peek :/
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.relationship
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||||
|
import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.whereType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relationship between rooms and spaces
|
||||||
|
* The intention is that rooms and spaces form a hierarchy, which clients can use to structure the user's room list into a tree view.
|
||||||
|
* The parent/child relationship can be expressed in one of two ways:
|
||||||
|
* - The admins of a space can advertise rooms and subspaces for their space by setting m.space.child state events.
|
||||||
|
* The state_key is the ID of a child room or space, and the content should contain a via key which gives
|
||||||
|
* a list of candidate servers that can be used to join the room. present: true key is included to distinguish from a deleted state event.
|
||||||
|
*
|
||||||
|
* - Separately, rooms can claim parents via the m.room.parent state event.
|
||||||
|
*/
|
||||||
|
internal class RoomChildRelationInfo(
|
||||||
|
private val realm: Realm,
|
||||||
|
private val roomId: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class SpaceChildInfo(
|
||||||
|
val roomId: String,
|
||||||
|
val order: String?,
|
||||||
|
val autoJoin: Boolean,
|
||||||
|
val viaServers: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SpaceParentInfo(
|
||||||
|
val roomId: String,
|
||||||
|
val canonical: Boolean,
|
||||||
|
val viaServers: List<String>,
|
||||||
|
val stateEventSender: String
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the ordered list of valid child description.
|
||||||
|
*/
|
||||||
|
fun getDirectChildrenDescriptions(): List<SpaceChildInfo> {
|
||||||
|
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
|
||||||
|
.findAll()
|
||||||
|
// .also {
|
||||||
|
// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
|
||||||
|
// }
|
||||||
|
.mapNotNull {
|
||||||
|
ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.let { scc ->
|
||||||
|
// Timber.v("## Space child desc state event $scc")
|
||||||
|
// Children where via is not present are ignored.
|
||||||
|
scc.via?.let { via ->
|
||||||
|
SpaceChildInfo(
|
||||||
|
roomId = it.stateKey,
|
||||||
|
order = scc.validOrder(),
|
||||||
|
autoJoin = scc.autoJoin ?: false,
|
||||||
|
viaServers = via
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedBy { it.order }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getParentDescriptions(): List<SpaceParentInfo> {
|
||||||
|
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
|
||||||
|
.findAll()
|
||||||
|
// .also {
|
||||||
|
// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
|
||||||
|
// }
|
||||||
|
.mapNotNull {
|
||||||
|
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { scc ->
|
||||||
|
// Timber.v("## Space parent desc state event $scc")
|
||||||
|
// Parent where via is not present are ignored.
|
||||||
|
scc.via?.let { via ->
|
||||||
|
SpaceParentInfo(
|
||||||
|
roomId = it.stateKey,
|
||||||
|
canonical = scc.canonical ?: false,
|
||||||
|
viaServers = via,
|
||||||
|
stateEventSender = it.root?.sender ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||||
|
|
||||||
internal interface EventSenderProcessor: SessionLifecycleObserver {
|
internal interface EventSenderProcessor: SessionLifecycleObserver {
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.failure.getRetryDelay
|
import org.matrix.android.sdk.api.failure.getRetryDelay
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
@ -72,7 +73,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
|
||||||
*/
|
*/
|
||||||
private val cancelableBag = ConcurrentHashMap<String, Cancelable>()
|
private val cancelableBag = ConcurrentHashMap<String, Cancelable>()
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
// We should check for sending events not handled because app was killed
|
// We should check for sending events not handled because app was killed
|
||||||
// But we should be careful of only took those that was submitted to us, because if it's
|
// But we should be careful of only took those that was submitted to us, because if it's
|
||||||
// for example it's a media event it is handled by some worker and he will handle it
|
// for example it's a media event it is handled by some worker and he will handle it
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.failure.isTokenError
|
import org.matrix.android.sdk.api.failure.isTokenError
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
|
@ -64,11 +65,11 @@ internal class EventSenderProcessorThread @Inject constructor(
|
||||||
memento.unTrack(task)
|
memento.unTrack(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStarted() {
|
override fun onSessionStarted(session: Session) {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionStopped() {
|
override fun onSessionStopped(session: Session) {
|
||||||
interrupt()
|
interrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||||
|
import java.lang.UnsupportedOperationException
|
||||||
|
|
||||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
|
@ -73,7 +74,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
eventType = eventType,
|
eventType = eventType,
|
||||||
body = body.toSafeJson(eventType)
|
body = body.toSafeJson(eventType)
|
||||||
)
|
)
|
||||||
sendStateTask.execute(params)
|
sendStateTask.executeRetry(params, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
|
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
|
||||||
|
@ -127,6 +128,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
|
|
||||||
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
|
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
|
||||||
if (joinRules != null) {
|
if (joinRules != null) {
|
||||||
|
if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
||||||
body = mapOf("join_rule" to joinRules),
|
body = mapOf("join_rule" to joinRules),
|
||||||
|
|
|
@ -21,22 +21,21 @@ import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
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.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class SerializablePowerLevelsContent(
|
internal data class SerializablePowerLevelsContent(
|
||||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
@Json(name = "ban") val ban: Int?,
|
||||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
@Json(name = "kick") val kick: Int?,
|
||||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
@Json(name = "invite") val invite: Int?,
|
||||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
@Json(name = "redact") val redact: Int?,
|
||||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
@Json(name = "events_default") val eventsDefault: Int?,
|
||||||
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
@Json(name = "events") val events: Map<String, Int>?,
|
||||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
@Json(name = "users_default") val usersDefault: Int?,
|
||||||
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
@Json(name = "users") val users: Map<String, Int>?,
|
||||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
@Json(name = "state_default") val stateDefault: Int?,
|
||||||
// `Int` is the diff here (instead of `Any`)
|
// `Int` is the diff here (instead of `Any`)
|
||||||
@Json(name = "notifications") val notifications: Map<String, Int> = emptyMap()
|
@Json(name = "notifications") val notifications: Map<String, Int>?
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
||||||
|
@ -52,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
||||||
usersDefault = content.usersDefault,
|
usersDefault = content.usersDefault,
|
||||||
users = content.users,
|
users = content.users,
|
||||||
stateDefault = content.stateDefault,
|
stateDefault = content.stateDefault,
|
||||||
notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
|
notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
?.toContent()
|
?.toContent()
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
data class GraphNode(
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GraphEdge(
|
||||||
|
val source: GraphNode,
|
||||||
|
val destination: GraphNode
|
||||||
|
)
|
||||||
|
|
||||||
|
class Graph {
|
||||||
|
|
||||||
|
private val adjacencyList: HashMap<GraphNode, ArrayList<GraphEdge>> = HashMap()
|
||||||
|
|
||||||
|
fun getOrCreateNode(name: String): GraphNode {
|
||||||
|
return adjacencyList.entries.firstOrNull { it.key.name == name }?.key
|
||||||
|
?: GraphNode(name).also {
|
||||||
|
adjacencyList[it] = ArrayList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEdge(sourceName: String, destinationName: String) {
|
||||||
|
val source = getOrCreateNode(sourceName)
|
||||||
|
val destination = getOrCreateNode(destinationName)
|
||||||
|
adjacencyList.getOrPut(source) { ArrayList() }.add(
|
||||||
|
GraphEdge(source, destination)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEdge(source: GraphNode, destination: GraphNode) {
|
||||||
|
adjacencyList.getOrPut(source) { ArrayList() }.add(
|
||||||
|
GraphEdge(source, destination)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun edgesOf(node: GraphNode): List<GraphEdge> {
|
||||||
|
return adjacencyList[node]?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withoutEdges(edgesToPrune: List<GraphEdge>): Graph {
|
||||||
|
val output = Graph()
|
||||||
|
this.adjacencyList.forEach { (vertex, edges) ->
|
||||||
|
output.getOrCreateNode(vertex.name)
|
||||||
|
edges.forEach {
|
||||||
|
if (!edgesToPrune.contains(it)) {
|
||||||
|
// add it
|
||||||
|
output.addEdge(it.source, it.destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the chosen starting point the background edge might change
|
||||||
|
*/
|
||||||
|
fun findBackwardEdges(startFrom: GraphNode? = null): List<GraphEdge> {
|
||||||
|
val backwardEdges = mutableSetOf<GraphEdge>()
|
||||||
|
val visited = mutableMapOf<GraphNode, Int>()
|
||||||
|
val notVisited = -1
|
||||||
|
val inPath = 0
|
||||||
|
val completed = 1
|
||||||
|
adjacencyList.keys.forEach {
|
||||||
|
visited[it] = notVisited
|
||||||
|
}
|
||||||
|
val stack = LinkedList<GraphNode>()
|
||||||
|
|
||||||
|
(startFrom ?: adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key)
|
||||||
|
?.let {
|
||||||
|
stack.push(it)
|
||||||
|
visited[it] = inPath
|
||||||
|
}
|
||||||
|
|
||||||
|
while (stack.isNotEmpty()) {
|
||||||
|
// Timber.w("VAL: current stack: ${stack.reversed().joinToString { it.name }}")
|
||||||
|
val vertex = stack.peek() ?: break
|
||||||
|
// peek a path to follow
|
||||||
|
var destination: GraphNode? = null
|
||||||
|
edgesOf(vertex).forEach {
|
||||||
|
when (visited[it.destination]) {
|
||||||
|
notVisited -> {
|
||||||
|
// it's a candidate
|
||||||
|
destination = it.destination
|
||||||
|
}
|
||||||
|
inPath -> {
|
||||||
|
// Cycle!!
|
||||||
|
backwardEdges.add(it)
|
||||||
|
}
|
||||||
|
completed -> {
|
||||||
|
// dead end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (destination == null) {
|
||||||
|
// dead end, pop
|
||||||
|
stack.pop().let {
|
||||||
|
visited[it] = completed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// go down this path
|
||||||
|
stack.push(destination)
|
||||||
|
visited[destination!!] = inPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
// try to get another graph of forest?
|
||||||
|
adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key?.let {
|
||||||
|
stack.push(it)
|
||||||
|
visited[it] = inPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return backwardEdges.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only call that on acyclic graph!
|
||||||
|
*/
|
||||||
|
fun flattenDestination(): Map<GraphNode, Set<GraphNode>> {
|
||||||
|
val result = HashMap<GraphNode, Set<GraphNode>>()
|
||||||
|
adjacencyList.keys.forEach { vertex ->
|
||||||
|
result[vertex] = flattenOf(vertex)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenOf(node: GraphNode): Set<GraphNode> {
|
||||||
|
val result = mutableSetOf<GraphNode>()
|
||||||
|
val edgesOf = edgesOf(node)
|
||||||
|
result.addAll(edgesOf.map { it.destination })
|
||||||
|
edgesOf.forEach {
|
||||||
|
result.addAll(flattenOf(it.destination))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return buildString {
|
||||||
|
adjacencyList.forEach { (node, edges) ->
|
||||||
|
append("${node.name} : [")
|
||||||
|
append(edges.joinToString(" ") { it.destination.name })
|
||||||
|
append("]\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
|
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.util.Optional
|
||||||
|
|
||||||
|
internal class HierarchyLiveDataHelper(
|
||||||
|
val spaceId: String,
|
||||||
|
val memberships: List<Membership>,
|
||||||
|
val roomSummaryDataSource: RoomSummaryDataSource) {
|
||||||
|
|
||||||
|
private val sources = HashMap<String, LiveData<Optional<RoomSummary>>>()
|
||||||
|
private val mediatorLiveData = MediatorLiveData<List<String>>()
|
||||||
|
|
||||||
|
fun liveData(): LiveData<List<String>> = mediatorLiveData
|
||||||
|
|
||||||
|
init {
|
||||||
|
onChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parentsToCheck(): List<RoomSummary> {
|
||||||
|
val spaces = ArrayList<RoomSummary>()
|
||||||
|
roomSummaryDataSource.getSpaceSummary(spaceId)?.let {
|
||||||
|
roomSummaryDataSource.flattenSubSpace(it, emptyList(), spaces, memberships)
|
||||||
|
}
|
||||||
|
return spaces
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onChange() {
|
||||||
|
val existingSources = sources.keys.toList()
|
||||||
|
val newSources = parentsToCheck().map { it.roomId }
|
||||||
|
val addedSources = newSources.filter { !existingSources.contains(it) }
|
||||||
|
val removedSource = existingSources.filter { !newSources.contains(it) }
|
||||||
|
addedSources.forEach {
|
||||||
|
val liveData = roomSummaryDataSource.getSpaceSummaryLive(it)
|
||||||
|
mediatorLiveData.addSource(liveData) { onChange() }
|
||||||
|
sources[it] = liveData
|
||||||
|
}
|
||||||
|
|
||||||
|
removedSource.forEach {
|
||||||
|
sources[it]?.let { mediatorLiveData.removeSource(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
sources[spaceId]?.value?.getOrNull()?.let { spaceSummary ->
|
||||||
|
val results = ArrayList<RoomSummary>()
|
||||||
|
roomSummaryDataSource.flattenChild(spaceSummary, emptyList(), results, memberships)
|
||||||
|
mediatorLiveData.postValue(results.map { it.roomId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.summary
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.paging.LivePagedListBuilder
|
import androidx.paging.LivePagedListBuilder
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
|
@ -24,12 +26,21 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||||
|
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
|
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||||
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||||
|
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.RoomSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
|
||||||
|
@ -80,11 +91,60 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
|
|
||||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ roomSummariesQuery(it, queryParams) },
|
{
|
||||||
|
roomSummariesQuery(it, queryParams)
|
||||||
|
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||||
|
},
|
||||||
{ roomSummaryMapper.map(it) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||||
|
return getRoomSummariesLive(queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
|
return getRoomSummary(roomIdOrAlias)
|
||||||
|
?.takeIf { it.roomType == RoomType.SPACE }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSpaceSummaryLive(roomId: String): LiveData<Optional<RoomSummary>> {
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
RoomSummaryEntity.where(realm, roomId)
|
||||||
|
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||||
|
.equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomSummaryMapper.map(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return Transformations.map(liveData) { results ->
|
||||||
|
results.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
|
||||||
|
return getRoomSummaries(spaceSummaryQueryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRootSpaceSummaries(): List<RoomSummary> {
|
||||||
|
return getRoomSummaries(spaceSummaryQueryParams {
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
})
|
||||||
|
.let { allJoinedSpace ->
|
||||||
|
val allFlattenChildren = arrayListOf<RoomSummary>()
|
||||||
|
allJoinedSpace.forEach {
|
||||||
|
flattenSubSpace(it, emptyList(), allFlattenChildren, listOf(Membership.JOIN), false)
|
||||||
|
}
|
||||||
|
val knownNonOrphan = allFlattenChildren.map { it.roomId }.distinct()
|
||||||
|
// keep only root rooms
|
||||||
|
allJoinedSpace.filter { candidate ->
|
||||||
|
!knownNonOrphan.contains(candidate.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||||
return monarchy.fetchAllMappedSync(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ breadcrumbsQuery(it, queryParams) },
|
{ breadcrumbsQuery(it, queryParams) },
|
||||||
|
@ -106,10 +166,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> {
|
pagedListConfig: PagedList.Config,
|
||||||
|
sortOrder: RoomSortOrder): LiveData<PagedList<RoomSummary>> {
|
||||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
roomSummariesQuery(realm, queryParams)
|
roomSummariesQuery(realm, queryParams).process(sortOrder)
|
||||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
|
||||||
}
|
}
|
||||||
val dataSourceFactory = realmDataSourceFactory.map {
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
roomSummaryMapper.map(it)
|
roomSummaryMapper.map(it)
|
||||||
|
@ -120,30 +180,48 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
|
pagedListConfig: PagedList.Config,
|
||||||
|
sortOrder: RoomSortOrder): UpdatableLivePageResult {
|
||||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
roomSummariesQuery(realm, queryParams)
|
roomSummariesQuery(realm, queryParams).process(sortOrder)
|
||||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
|
||||||
}
|
}
|
||||||
val dataSourceFactory = realmDataSourceFactory.map {
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
roomSummaryMapper.map(it)
|
roomSummaryMapper.map(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val boundaries = MutableLiveData(ResultBoundaries())
|
||||||
|
|
||||||
val mapped = monarchy.findAllPagedWithChanges(
|
val mapped = monarchy.findAllPagedWithChanges(
|
||||||
realmDataSourceFactory,
|
realmDataSourceFactory,
|
||||||
LivePagedListBuilder(dataSourceFactory, pagedListConfig)
|
LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
|
||||||
|
it.setBoundaryCallback(object : PagedList.BoundaryCallback<RoomSummary>() {
|
||||||
|
override fun onItemAtEndLoaded(itemAtEnd: RoomSummary) {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemAtFrontLoaded(itemAtFront: RoomSummary) {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onZeroItemsLoaded() {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return object : UpdatableFilterLivePageResult {
|
return object : UpdatableLivePageResult {
|
||||||
override val livePagedList: LiveData<PagedList<RoomSummary>> = mapped
|
override val livePagedList: LiveData<PagedList<RoomSummary>> = mapped
|
||||||
|
|
||||||
override fun updateQuery(queryParams: RoomSummaryQueryParams) {
|
override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
|
||||||
realmDataSourceFactory.updateQuery {
|
realmDataSourceFactory.updateQuery {
|
||||||
roomSummariesQuery(it, queryParams)
|
roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
|
||||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val liveBoundaries: LiveData<ResultBoundaries>
|
||||||
|
get() = boundaries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,12 +254,12 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
|
|
||||||
queryParams.roomCategoryFilter?.let {
|
queryParams.roomCategoryFilter?.let {
|
||||||
when (it) {
|
when (it) {
|
||||||
RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.beginGroup()
|
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.beginGroup()
|
||||||
.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
|
.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
|
||||||
.equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
|
.equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
|
||||||
RoomCategoryFilter.ALL -> {
|
RoomCategoryFilter.ALL -> {
|
||||||
// nop
|
// nop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,6 +275,162 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||||
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
|
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryParams.excludeType?.forEach {
|
||||||
|
query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it)
|
||||||
|
}
|
||||||
|
queryParams.includeType?.forEach {
|
||||||
|
query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it)
|
||||||
|
}
|
||||||
|
when (queryParams.roomCategoryFilter) {
|
||||||
|
RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
|
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.beginGroup()
|
||||||
|
.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
|
||||||
|
.equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
|
||||||
|
RoomCategoryFilter.ALL -> Unit // nop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
|
||||||
|
when (queryParams.activeSpaceId) {
|
||||||
|
is ActiveSpaceFilter.ActiveSpace -> {
|
||||||
|
// It's annoying but for now realm java does not support querying in primitive list :/
|
||||||
|
// https://github.com/realm/realm-java/issues/5361
|
||||||
|
if (queryParams.activeSpaceId.currentSpaceId == null) {
|
||||||
|
// orphan rooms
|
||||||
|
query.isNull(RoomSummaryEntityFields.FLATTEN_PARENT_IDS)
|
||||||
|
} else {
|
||||||
|
query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.currentSpaceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ActiveSpaceFilter.ExcludeSpace -> {
|
||||||
|
query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.spaceId)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.activeGroupId != null) {
|
||||||
|
query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!)
|
||||||
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllRoomSummaryChildOf(spaceAliasOrId: String, memberShips: List<Membership>): List<RoomSummary> {
|
||||||
|
val space = getSpaceSummary(spaceAliasOrId) ?: return emptyList()
|
||||||
|
val result = ArrayList<RoomSummary>()
|
||||||
|
flattenChild(space, emptyList(), result, memberShips)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllRoomSummaryChildOfLive(spaceId: String, memberShips: List<Membership>): LiveData<List<RoomSummary>> {
|
||||||
|
// we want to listen to all spaces in hierarchy and on change compute back all childs
|
||||||
|
// and switch map to listen those?
|
||||||
|
val mediatorLiveData = HierarchyLiveDataHelper(spaceId, memberShips, this).liveData()
|
||||||
|
|
||||||
|
return Transformations.switchMap(mediatorLiveData) { allIds ->
|
||||||
|
monarchy.findAllMappedWithChanges(
|
||||||
|
{
|
||||||
|
it.where<RoomSummaryEntity>()
|
||||||
|
.`in`(RoomSummaryEntityFields.ROOM_ID, allIds.toTypedArray())
|
||||||
|
.`in`(RoomSummaryEntityFields.MEMBERSHIP_STR, memberShips.map { it.name }.toTypedArray())
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomSummaryMapper.map(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFlattenOrphanRooms(): List<RoomSummary> {
|
||||||
|
return getRoomSummaries(
|
||||||
|
roomSummaryQueryParams {
|
||||||
|
memberships = Membership.activeMemberships()
|
||||||
|
excludeType = listOf(RoomType.SPACE)
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
}
|
||||||
|
).filter { isOrphan(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFlattenOrphanRoomsLive(): LiveData<List<RoomSummary>> {
|
||||||
|
return Transformations.map(
|
||||||
|
getRoomSummariesLive(roomSummaryQueryParams {
|
||||||
|
memberships = Membership.activeMemberships()
|
||||||
|
excludeType = listOf(RoomType.SPACE)
|
||||||
|
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
it.filter { isOrphan(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isOrphan(roomSummary: RoomSummary): Boolean {
|
||||||
|
if (roomSummary.roomType == RoomType.SPACE && roomSummary.membership.isActive()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// all parents line should be orphan
|
||||||
|
roomSummary.spaceParents?.forEach { info ->
|
||||||
|
if (info.roomSummary != null && !info.roomSummary.membership.isLeft()) {
|
||||||
|
if (!isOrphan(info.roomSummary)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it may not have a parent relation but could be a child of some other....
|
||||||
|
for (spaceSummary in getSpaceSummaries(spaceSummaryQueryParams { memberships = Membership.activeMemberships() })) {
|
||||||
|
if (spaceSummary.spaceChildren?.any { it.childRoomId == roomSummary.roomId } == true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flattenChild(current: RoomSummary, parenting: List<String>, output: MutableList<RoomSummary>, memberShips: List<Membership>) {
|
||||||
|
current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach { childInfo ->
|
||||||
|
if (childInfo.roomType == RoomType.SPACE) {
|
||||||
|
// Add recursive
|
||||||
|
if (!parenting.contains(childInfo.childRoomId)) { // avoid cycles!
|
||||||
|
getSpaceSummary(childInfo.childRoomId)?.let { subSpace ->
|
||||||
|
if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
|
||||||
|
flattenChild(subSpace, parenting + listOf(current.roomId), output, memberShips)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (childInfo.isKnown) {
|
||||||
|
getRoomSummary(childInfo.childRoomId)?.let {
|
||||||
|
if (memberShips.isEmpty() || memberShips.contains(it.membership)) {
|
||||||
|
if (!it.isDirect) {
|
||||||
|
output.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flattenSubSpace(current: RoomSummary,
|
||||||
|
parenting: List<String>,
|
||||||
|
output: MutableList<RoomSummary>,
|
||||||
|
memberShips: List<Membership>,
|
||||||
|
includeCurrent: Boolean = true) {
|
||||||
|
if (includeCurrent) {
|
||||||
|
output.add(current)
|
||||||
|
}
|
||||||
|
current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach {
|
||||||
|
if (it.roomType == RoomType.SPACE) {
|
||||||
|
// Add recursive
|
||||||
|
if (!parenting.contains(it.childRoomId)) { // avoid cycles!
|
||||||
|
getSpaceSummary(it.childRoomId)?.let { subSpace ->
|
||||||
|
if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
|
||||||
|
output.add(subSpace)
|
||||||
|
flattenSubSpace(subSpace, parenting + listOf(current.roomId), output, memberShips)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.summary
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
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.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -25,6 +26,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
@ -34,29 +37,40 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
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.EventEntityFields
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||||
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.SpaceChildSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity
|
||||||
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.query.findAllInRoomWithSendStates
|
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.database.query.isEventRead
|
import org.matrix.android.sdk.internal.database.query.isEventRead
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.database.query.whereType
|
import org.matrix.android.sdk.internal.database.query.whereType
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.extensions.clearWith
|
||||||
|
import org.matrix.android.sdk.internal.query.process
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
|
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
|
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
|
||||||
|
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
|
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
internal class RoomSummaryUpdater @Inject constructor(
|
internal class RoomSummaryUpdater @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||||
private val roomAvatarResolver: RoomAvatarResolver,
|
private val roomAvatarResolver: RoomAvatarResolver,
|
||||||
private val eventDecryptor: EventDecryptor,
|
private val eventDecryptor: EventDecryptor,
|
||||||
private val crossSigningService: DefaultCrossSigningService) {
|
private val crossSigningService: DefaultCrossSigningService,
|
||||||
|
private val stateEventDataSource: StateEventDataSource) {
|
||||||
|
|
||||||
fun update(realm: Realm,
|
fun update(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
@ -89,6 +103,11 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||||
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
||||||
|
val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root
|
||||||
|
|
||||||
|
val roomType = ContentMapper.map(roomCreateEvent?.content).toModel<RoomCreateContent>()?.type
|
||||||
|
roomSummaryEntity.roomType = roomType
|
||||||
|
Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
|
||||||
|
|
||||||
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
|
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
|
||||||
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||||
|
@ -177,4 +196,238 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)
|
roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be called at the end of the room sync, to check and validate all parent/child relations
|
||||||
|
*/
|
||||||
|
fun validateSpaceRelationship(realm: Realm) {
|
||||||
|
measureTimeMillis {
|
||||||
|
val lookupMap = realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
|
// we order by roomID to be consistent when breaking parent/child cycles
|
||||||
|
.sort(RoomSummaryEntityFields.ROOM_ID)
|
||||||
|
.findAll().map {
|
||||||
|
it.flattenParentIds = null
|
||||||
|
it to emptyList<RoomSummaryEntity>().toMutableSet()
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
|
||||||
|
lookupMap.keys.forEach { lookedUp ->
|
||||||
|
if (lookedUp.roomType == RoomType.SPACE) {
|
||||||
|
// get childrens
|
||||||
|
|
||||||
|
lookedUp.children.clearWith { it.deleteFromRealm() }
|
||||||
|
|
||||||
|
RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
|
||||||
|
|
||||||
|
lookedUp.children.add(
|
||||||
|
realm.createObject<SpaceChildSummaryEntity>().apply {
|
||||||
|
this.childRoomId = child.roomId
|
||||||
|
this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
|
||||||
|
this.order = child.order
|
||||||
|
this.autoJoin = child.autoJoin
|
||||||
|
this.viaServers.addAll(child.viaServers)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
RoomSummaryEntity.where(realm, child.roomId)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.findFirst()
|
||||||
|
?.let { childSum ->
|
||||||
|
lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
|
||||||
|
if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
|
||||||
|
// add looked up as a parent
|
||||||
|
entry.value.add(childSum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lookedUp.parents.clearWith { it.deleteFromRealm() }
|
||||||
|
// can we check parent relations here??
|
||||||
|
RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
|
||||||
|
.map { parentInfo ->
|
||||||
|
|
||||||
|
lookedUp.parents.add(
|
||||||
|
realm.createObject<SpaceParentSummaryEntity>().apply {
|
||||||
|
this.parentRoomId = parentInfo.roomId
|
||||||
|
this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
|
||||||
|
this.canonical = parentInfo.canonical
|
||||||
|
this.viaServers.addAll(parentInfo.viaServers)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
RoomSummaryEntity.where(realm, parentInfo.roomId)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.findFirst()
|
||||||
|
?.let { parentSum ->
|
||||||
|
if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
|
||||||
|
// add lookedup as a parent
|
||||||
|
lookupMap[parentSum]?.add(lookedUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple algorithm to break cycles
|
||||||
|
// Need more work to decide how to break, probably need to be as consistent as possible
|
||||||
|
// and also find best way to root the tree
|
||||||
|
|
||||||
|
val graph = Graph()
|
||||||
|
lookupMap
|
||||||
|
// focus only on joined spaces, as room are just leaf
|
||||||
|
.filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
|
||||||
|
.forEach { (sum, children) ->
|
||||||
|
graph.getOrCreateNode(sum.roomId)
|
||||||
|
children.forEach {
|
||||||
|
graph.addEdge(it.roomId, sum.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val backEdges = graph.findBackwardEdges()
|
||||||
|
Timber.v("## SPACES: Cycle detected = ${backEdges.isNotEmpty()}")
|
||||||
|
|
||||||
|
// break cycles
|
||||||
|
backEdges.forEach { edge ->
|
||||||
|
lookupMap.entries.find { it.key.roomId == edge.source.name }?.let {
|
||||||
|
it.value.removeAll { it.roomId == edge.destination.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val acyclicGraph = graph.withoutEdges(backEdges)
|
||||||
|
// Timber.v("## SPACES: acyclicGraph $acyclicGraph")
|
||||||
|
val flattenSpaceParents = acyclicGraph.flattenDestination().map {
|
||||||
|
it.key.name to it.value.map { it.name }
|
||||||
|
}.toMap()
|
||||||
|
// Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") {
|
||||||
|
// it.first + ": [" + it.second.joinToString(",") + "]"
|
||||||
|
// }}")
|
||||||
|
|
||||||
|
// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
|
||||||
|
|
||||||
|
lookupMap.entries
|
||||||
|
.filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
|
||||||
|
.forEach { entry ->
|
||||||
|
val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
|
||||||
|
if (parent != null) {
|
||||||
|
// Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}")
|
||||||
|
// Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}")
|
||||||
|
val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId)
|
||||||
|
// Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}")
|
||||||
|
entry.value.forEach { child ->
|
||||||
|
RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum ->
|
||||||
|
|
||||||
|
// Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}")
|
||||||
|
// var allParents = childSum.flattenParentIds ?: ""
|
||||||
|
if (childSum.flattenParentIds == null) childSum.flattenParentIds = ""
|
||||||
|
flattenParentsIds.forEach {
|
||||||
|
if (childSum.flattenParentIds?.contains(it) != true) {
|
||||||
|
childSum.flattenParentIds += "|$it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// childSum.flattenParentIds = "$allParents|"
|
||||||
|
|
||||||
|
// Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need also to filter DMs...
|
||||||
|
// it's more annoying as based on if the other members belong the space or not
|
||||||
|
RoomSummaryEntity.where(realm)
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.findAll()
|
||||||
|
.forEach { dmRoom ->
|
||||||
|
val relatedSpaces = lookupMap.keys
|
||||||
|
.filter { it.roomType == RoomType.SPACE }
|
||||||
|
.filter {
|
||||||
|
dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toList()).isNotEmpty()
|
||||||
|
}
|
||||||
|
.map { it.roomId }
|
||||||
|
.distinct()
|
||||||
|
val flattenRelated = mutableListOf<String>().apply {
|
||||||
|
addAll(relatedSpaces)
|
||||||
|
relatedSpaces.map { flattenSpaceParents[it] }.forEach {
|
||||||
|
if (it != null) addAll(it)
|
||||||
|
}
|
||||||
|
}.distinct()
|
||||||
|
if (flattenRelated.isEmpty()) {
|
||||||
|
dmRoom.flattenParentIds = null
|
||||||
|
} else {
|
||||||
|
dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
|
||||||
|
}
|
||||||
|
// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe a good place to count the number of notifications for spaces?
|
||||||
|
|
||||||
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
|
||||||
|
.findAll().forEach { space ->
|
||||||
|
// get all children
|
||||||
|
var highlightCount = 0
|
||||||
|
var notificationCount = 0
|
||||||
|
var unreadCount = 0
|
||||||
|
var markedUnreadCount = 0
|
||||||
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
|
||||||
|
.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
|
||||||
|
.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
|
||||||
|
.findAll().forEach {
|
||||||
|
highlightCount += it.highlightCount
|
||||||
|
notificationCount += it.notificationCount
|
||||||
|
unreadCount += if (it.hasUnreadOriginalContentMessages) 1 else 0
|
||||||
|
markedUnreadCount += if (it.markedUnread) 1 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
space.highlightCount = highlightCount
|
||||||
|
space.notificationCount = notificationCount + markedUnreadCount
|
||||||
|
space.unreadCount = unreadCount
|
||||||
|
}
|
||||||
|
// xxx invites??
|
||||||
|
|
||||||
|
// LEGACY GROUPS
|
||||||
|
// lets mark rooms that belongs to groups
|
||||||
|
val existingGroups = GroupSummaryEntity.where(realm).findAll()
|
||||||
|
|
||||||
|
// For rooms
|
||||||
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||||
|
.findAll().forEach { room ->
|
||||||
|
val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) }
|
||||||
|
room.groupIds = if (belongsTo.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
"|${belongsTo.joinToString("|")}|"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For DMS
|
||||||
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
.findAll().forEach { room ->
|
||||||
|
val belongsTo = existingGroups.filter {
|
||||||
|
it.userIds.intersect(room.otherMemberIds).isNotEmpty()
|
||||||
|
}
|
||||||
|
room.groupIds = if (belongsTo.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
"|${belongsTo.joinToString("|")}|"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun isValidCanonical() : Boolean {
|
||||||
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
||||||
}
|
}
|
||||||
?: ChunkEntity.create(realm, prevToken, nextToken)
|
?: ChunkEntity.create(realm, prevToken, nextToken)
|
||||||
|
|
||||||
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
|
if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) {
|
||||||
handleReachEnd(realm, roomId, direction, currentChunk)
|
handleReachEnd(realm, roomId, direction, currentChunk)
|
||||||
} else {
|
} else {
|
||||||
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)
|
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.space
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
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.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||||
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
|
|
||||||
|
internal class DefaultSpace(
|
||||||
|
private val room: Room,
|
||||||
|
private val spaceSummaryDataSource: RoomSummaryDataSource
|
||||||
|
) : Space {
|
||||||
|
|
||||||
|
override fun asRoom(): Room {
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
|
||||||
|
override val spaceId = room.roomId
|
||||||
|
|
||||||
|
override suspend fun leave(reason: String?) {
|
||||||
|
return room.leave(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun spaceSummary(): RoomSummary? {
|
||||||
|
return spaceSummaryDataSource.getSpaceSummary(room.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addChildren(roomId: String,
|
||||||
|
viaServers: List<String>,
|
||||||
|
order: String?,
|
||||||
|
autoJoin: Boolean,
|
||||||
|
suggested: Boolean?) {
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_CHILD,
|
||||||
|
stateKey = roomId,
|
||||||
|
body = SpaceChildContent(
|
||||||
|
via = viaServers,
|
||||||
|
autoJoin = autoJoin,
|
||||||
|
order = order,
|
||||||
|
suggested = suggested
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeChildren(roomId: String) {
|
||||||
|
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||||
|
.firstOrNull()
|
||||||
|
?.content.toModel<SpaceChildContent>()
|
||||||
|
?: // should we throw here?
|
||||||
|
return
|
||||||
|
|
||||||
|
// edit state event and set via to null
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_CHILD,
|
||||||
|
stateKey = roomId,
|
||||||
|
body = SpaceChildContent(
|
||||||
|
order = existing.order,
|
||||||
|
via = null,
|
||||||
|
autoJoin = existing.autoJoin
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setChildrenOrder(roomId: String, order: String?) {
|
||||||
|
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||||
|
.firstOrNull()
|
||||||
|
?.content.toModel<SpaceChildContent>()
|
||||||
|
?: throw IllegalArgumentException("$roomId is not a child of this space")
|
||||||
|
|
||||||
|
// edit state event and set via to null
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_CHILD,
|
||||||
|
stateKey = roomId,
|
||||||
|
body = SpaceChildContent(
|
||||||
|
order = order,
|
||||||
|
via = existing.via,
|
||||||
|
autoJoin = existing.autoJoin
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) {
|
||||||
|
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
|
||||||
|
.firstOrNull()
|
||||||
|
?.content.toModel<SpaceChildContent>()
|
||||||
|
?: throw IllegalArgumentException("$roomId is not a child of this space")
|
||||||
|
|
||||||
|
// edit state event and set via to null
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_CHILD,
|
||||||
|
stateKey = roomId,
|
||||||
|
body = SpaceChildContent(
|
||||||
|
order = existing.order,
|
||||||
|
via = existing.via,
|
||||||
|
autoJoin = autoJoin
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.space
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
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.model.GuestAccess
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||||
|
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||||
|
import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
|
||||||
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
||||||
|
import org.matrix.android.sdk.internal.session.room.SpaceGetter
|
||||||
|
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||||
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
|
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
||||||
|
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DefaultSpaceService @Inject constructor(
|
||||||
|
@UserId private val userId: String,
|
||||||
|
private val createRoomTask: CreateRoomTask,
|
||||||
|
private val joinSpaceTask: JoinSpaceTask,
|
||||||
|
private val spaceGetter: SpaceGetter,
|
||||||
|
private val roomGetter: RoomGetter,
|
||||||
|
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||||
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
|
private val peekSpaceTask: PeekSpaceTask,
|
||||||
|
private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
|
||||||
|
private val leaveRoomTask: LeaveRoomTask
|
||||||
|
) : SpaceService {
|
||||||
|
|
||||||
|
override suspend fun createSpace(params: CreateSpaceParams): String {
|
||||||
|
return createRoomTask.executeRetry(params, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
|
||||||
|
return createSpace(CreateSpaceParams().apply {
|
||||||
|
this.name = name
|
||||||
|
this.topic = topic
|
||||||
|
this.avatarUri = avatarUri
|
||||||
|
if (isPublic) {
|
||||||
|
this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
|
||||||
|
invite = 0
|
||||||
|
)
|
||||||
|
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
|
this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
|
||||||
|
this.guestAccess = GuestAccess.CanJoin
|
||||||
|
} else {
|
||||||
|
this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
|
||||||
|
visibility = RoomDirectoryVisibility.PRIVATE
|
||||||
|
enableEncryption()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSpace(spaceId: String): Space? {
|
||||||
|
return spaceGetter.get(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||||
|
return roomSummaryDataSource.getSpaceSummariesLive(queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
|
||||||
|
return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRootSpaceSummaries(): List<RoomSummary> {
|
||||||
|
return roomSummaryDataSource.getRootSpaceSummaries()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun peekSpace(spaceId: String): SpacePeekResult {
|
||||||
|
return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun querySpaceChildren(spaceId: String,
|
||||||
|
suggestedOnly: Boolean?,
|
||||||
|
autoJoinedOnly: Boolean?): Pair<RoomSummary, List<SpaceChildInfo>> {
|
||||||
|
return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response ->
|
||||||
|
val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId }
|
||||||
|
Pair(
|
||||||
|
first = RoomSummary(
|
||||||
|
roomId = spaceDesc?.roomId ?: spaceId,
|
||||||
|
roomType = spaceDesc?.roomType,
|
||||||
|
name = spaceDesc?.name ?: "",
|
||||||
|
displayName = spaceDesc?.name ?: "",
|
||||||
|
topic = spaceDesc?.topic ?: "",
|
||||||
|
joinedMembersCount = spaceDesc?.numJoinedMembers,
|
||||||
|
avatarUrl = spaceDesc?.avatarUrl ?: "",
|
||||||
|
encryptionEventTs = null,
|
||||||
|
typingUsers = emptyList(),
|
||||||
|
isEncrypted = false,
|
||||||
|
flattenParentIds = emptyList()
|
||||||
|
),
|
||||||
|
second = response.rooms
|
||||||
|
?.filter { it.roomId != spaceId }
|
||||||
|
?.map { childSummary ->
|
||||||
|
val childStateEv = response.events
|
||||||
|
?.firstOrNull { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
|
||||||
|
val childStateEvContent = childStateEv?.content.toModel<SpaceChildContent>()
|
||||||
|
SpaceChildInfo(
|
||||||
|
childRoomId = childSummary.roomId,
|
||||||
|
isKnown = true,
|
||||||
|
roomType = childSummary.roomType,
|
||||||
|
name = childSummary.name,
|
||||||
|
topic = childSummary.topic,
|
||||||
|
avatarUrl = childSummary.avatarUrl,
|
||||||
|
order = childStateEvContent?.order,
|
||||||
|
autoJoin = childStateEvContent?.autoJoin ?: false,
|
||||||
|
viaServers = childStateEvContent?.via ?: emptyList(),
|
||||||
|
activeMemberCount = childSummary.numJoinedMembers,
|
||||||
|
parentRoomId = childStateEv?.roomId
|
||||||
|
)
|
||||||
|
}.orEmpty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun joinSpace(spaceIdOrAlias: String,
|
||||||
|
reason: String?,
|
||||||
|
viaServers: List<String>): JoinSpaceResult {
|
||||||
|
return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rejectInvite(spaceId: String, reason: String?) {
|
||||||
|
leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
// override fun getSpaceParentsOfRoom(roomId: String): List<SpaceSummary> {
|
||||||
|
// return spaceSummaryDataSource.getParentsOfRoom(roomId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>) {
|
||||||
|
// Should we perform some validation here?,
|
||||||
|
// and if client want to bypass, it could use sendStateEvent directly?
|
||||||
|
if (canonical) {
|
||||||
|
// check that we can send m.child in the parent room
|
||||||
|
if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) {
|
||||||
|
throw UnsupportedOperationException("Cannot add canonical child if not member of parent")
|
||||||
|
}
|
||||||
|
val powerLevelsEvent = stateEventDataSource.getStateEvent(
|
||||||
|
roomId = parentSpaceId,
|
||||||
|
eventType = EventType.STATE_ROOM_POWER_LEVELS,
|
||||||
|
stateKey = QueryStringValue.NoCondition
|
||||||
|
)
|
||||||
|
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>()
|
||||||
|
?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel")
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
|
||||||
|
if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) {
|
||||||
|
throw UnsupportedOperationException("Cannot add canonical child, not enough power level")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val room = roomGetter.getRoom(childRoomId)
|
||||||
|
?: throw IllegalArgumentException("Unknown Room $childRoomId")
|
||||||
|
|
||||||
|
room.sendStateEvent(
|
||||||
|
eventType = EventType.STATE_SPACE_PARENT,
|
||||||
|
stateKey = parentSpaceId,
|
||||||
|
body = SpaceParentContent(
|
||||||
|
via = viaServers,
|
||||||
|
canonical = canonical
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.session.space
|
||||||
|
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.space.JoinSpaceResult
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface JoinSpaceTask : Task<JoinSpaceTask.Params, JoinSpaceResult> {
|
||||||
|
data class Params(
|
||||||
|
val roomIdOrAlias: String,
|
||||||
|
val reason: String?,
|
||||||
|
val viaServers: List<String> = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultJoinSpaceTask @Inject constructor(
|
||||||
|
private val joinRoomTask: JoinRoomTask,
|
||||||
|
@SessionDatabase
|
||||||
|
private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val roomSummaryDataSource: RoomSummaryDataSource
|
||||||
|
) : JoinSpaceTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: JoinSpaceTask.Params): JoinSpaceResult {
|
||||||
|
Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...")
|
||||||
|
try {
|
||||||
|
joinRoomTask.execute(JoinRoomTask.Params(
|
||||||
|
params.roomIdOrAlias,
|
||||||
|
params.reason,
|
||||||
|
params.viaServers
|
||||||
|
))
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return JoinSpaceResult.Fail(failure)
|
||||||
|
}
|
||||||
|
Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}")
|
||||||
|
// we want to wait for sync result to check for auto join rooms
|
||||||
|
|
||||||
|
Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
|
||||||
|
try {
|
||||||
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm ->
|
||||||
|
realm.where(RoomSummaryEntity::class.java)
|
||||||
|
.apply {
|
||||||
|
if (params.roomIdOrAlias.startsWith("!")) {
|
||||||
|
equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias)
|
||||||
|
} else {
|
||||||
|
equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
|
}
|
||||||
|
} catch (exception: TimeoutCancellationException) {
|
||||||
|
Timber.w("## Space: > Error created with timeout")
|
||||||
|
return JoinSpaceResult.PartialSuccess(emptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
val errors = mutableMapOf<String, Throwable>()
|
||||||
|
Timber.v("## Space: > Sync done ...")
|
||||||
|
// after that i should have the children (? do I need to paginate to get state)
|
||||||
|
val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias)
|
||||||
|
Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}")
|
||||||
|
summary?.spaceChildren?.forEach {
|
||||||
|
// val childRoomSummary = it.roomSummary ?: return@forEach
|
||||||
|
Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}")
|
||||||
|
if (it.autoJoin) {
|
||||||
|
// I should try to join as well
|
||||||
|
if (it.roomType == RoomType.SPACE) {
|
||||||
|
// recursively join auto-joined child of this space?
|
||||||
|
when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) {
|
||||||
|
JoinSpaceResult.Success -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
is JoinSpaceResult.Fail -> {
|
||||||
|
errors[it.childRoomId] = subspaceJoinResult.error
|
||||||
|
}
|
||||||
|
is JoinSpaceResult.PartialSuccess -> {
|
||||||
|
errors.putAll(subspaceJoinResult.failedRooms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Timber.v("## Space: Joining room child ${it.childRoomId}")
|
||||||
|
joinRoomTask.execute(JoinRoomTask.Params(
|
||||||
|
roomIdOrAlias = it.childRoomId,
|
||||||
|
reason = "Auto-join parent space",
|
||||||
|
viaServers = it.viaServers
|
||||||
|
))
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
errors[it.childRoomId] = failure
|
||||||
|
Timber.e("## Space: Failed to join room child ${it.childRoomId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (errors.isEmpty()) {
|
||||||
|
JoinSpaceResult.Success
|
||||||
|
} else {
|
||||||
|
JoinSpaceResult.PartialSuccess(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
|
// realm.where(RoomEntity::class.java)
|
||||||
|
// .equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||||
|
// }
|
||||||
|
// } catch (exception: TimeoutCancellationException) {
|
||||||
|
// throw CreateRoomFailure.CreatedWithTimeout
|
||||||
|
// }
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue