mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-29 06:28:45 +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)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
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'
|
||||
}
|
|
@ -16,8 +16,8 @@ buildscript {
|
|||
classpath 'com.google.gms:google-services:4.3.5'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3'
|
||||
classpath "com.likethesalad.android:string-reference:1.2.1"
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
|
||||
classpath "com.likethesalad.android:string-reference:1.2.2"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -51,7 +51,7 @@ allprojects {
|
|||
}
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/piasy/maven"
|
||||
url "https://dl.bintray.com/piasy/maven"
|
||||
content {
|
||||
includeGroupByRegex "com\\.github\\.piasy"
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ allprojects {
|
|||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
// Jitsi repo
|
||||
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:
|
||||
// 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
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
|
||||
distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.matrix.android.sdk.rx" />
|
||||
<manifest 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.RoomSummary
|
||||
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.user.model.User
|
||||
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>> {
|
||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
|
@ -124,8 +132,8 @@ class RxSession(private val session: Session) {
|
|||
.startWithCallable { session.getPendingThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = rxSingle {
|
||||
session.createRoom(roomParams)
|
||||
}
|
||||
|
||||
fun searchUsersDirectory(search: String,
|
||||
|
@ -136,13 +144,13 @@ class RxSession(private val session: Session) {
|
|||
|
||||
fun joinRoom(roomIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||
session.joinRoom(roomIdOrAlias, reason, viaServers, it)
|
||||
viaServers: List<String> = emptyList()): Single<Unit> = rxSingle {
|
||||
session.joinRoom(roomIdOrAlias, reason, viaServers)
|
||||
}
|
||||
|
||||
fun getRoomIdByAlias(roomAlias: String,
|
||||
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = singleBuilder {
|
||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = rxSingle {
|
||||
session.getRoomIdByAlias(roomAlias, searchOnServer)
|
||||
}
|
||||
|
||||
fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle {
|
||||
|
|
|
@ -115,7 +115,7 @@ dependencies {
|
|||
def lifecycle_version = '2.2.0'
|
||||
def arch_version = '2.1.0'
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.33'
|
||||
def daggerVersion = '2.35'
|
||||
def work_version = '2.5.0'
|
||||
def retrofit_version = '2.9.0'
|
||||
|
||||
|
@ -169,7 +169,7 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
|
||||
|
||||
// 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 'org.robolectric:robolectric:4.5.1'
|
||||
|
|
|
@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
}
|
||||
|
||||
if (encryptedRoom) {
|
||||
|
@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> { bobSession.joinRoom(aliceRoomId, callback = it) }
|
||||
mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
|
||||
|
||||
mTestHelper.await(lock)
|
||||
|
||||
|
@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
room.invite(samSession.myUserId, null)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
samSession.joinRoom(room.roomId, null, emptyList(), it)
|
||||
mTestHelper.runBlockingTest {
|
||||
samSession.joinRoom(room.roomId, null, emptyList())
|
||||
}
|
||||
|
||||
return samSession
|
||||
|
@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
}
|
||||
|
||||
fun createDM(alice: Session, bob: Session): String {
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
alice.createDirectRoom(bob.myUserId, it)
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
alice.createDirectRoom(bob.myUserId)
|
||||
}
|
||||
|
||||
mTestHelper.waitWithLatch { latch ->
|
||||
|
@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> { bob.joinRoom(roomId, callback = it) }
|
||||
mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
|
||||
}
|
||||
|
||||
return roomId
|
||||
|
@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
|
@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
||||
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
|
||||
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")
|
||||
sessions.add(session)
|
||||
}
|
||||
|
|
|
@ -71,13 +71,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
// Create an encrypted room and add a message
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
aliceSession.createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
},
|
||||
it
|
||||
}
|
||||
)
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)
|
||||
|
@ -332,13 +331,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
// Create an encrypted room and send a couple of messages
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
val roomId = mTestHelper.runBlockingTest {
|
||||
aliceSession.createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
},
|
||||
it
|
||||
}
|
||||
)
|
||||
}
|
||||
val roomAlicePov = aliceSession.getRoom(roomId)
|
||||
|
@ -371,8 +369,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
roomAlicePov.invite(bobSession.myUserId, null)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
|
||||
mTestHelper.runBlockingTest {
|
||||
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
data class LoginFlowResult(
|
||||
val supportedLoginTypes: List<String>,
|
||||
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean
|
||||
) : LoginFlowResult()
|
||||
}
|
||||
)
|
||||
|
|
|
@ -20,7 +20,9 @@ interface RegistrationWizard {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import java.io.IOException
|
|||
*/
|
||||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
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 NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
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.SharedSecretStorageService
|
||||
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.SyncState
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
|
@ -227,6 +228,11 @@ interface Session :
|
|||
*/
|
||||
fun thirdPartyService(): ThirdPartyService
|
||||
|
||||
/**
|
||||
* Returns the space service associated with the session
|
||||
*/
|
||||
fun spaceService(): SpaceService
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
|
@ -249,13 +255,13 @@ interface Session :
|
|||
/**
|
||||
* A global session listener to get notified for some events.
|
||||
*/
|
||||
interface Listener {
|
||||
interface Listener : SessionLifecycleObserver {
|
||||
/**
|
||||
* Possible cases:
|
||||
* - The access token is not valid anymore,
|
||||
* - 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
|
||||
|
|
|
@ -14,20 +14,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session
|
||||
package org.matrix.android.sdk.api.session
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@MainThread
|
||||
fun onSessionStarted() {
|
||||
fun onSessionStarted(session: Session) {
|
||||
// noop
|
||||
}
|
||||
|
||||
|
@ -35,7 +34,7 @@ internal interface SessionLifecycleObserver {
|
|||
Called when the session is cleared
|
||||
*/
|
||||
@MainThread
|
||||
fun onClearCache() {
|
||||
fun onClearCache(session: Session) {
|
||||
// noop
|
||||
}
|
||||
|
||||
|
@ -43,7 +42,7 @@ internal interface SessionLifecycleObserver {
|
|||
Called when the session is closed
|
||||
*/
|
||||
@MainThread
|
||||
fun onSessionStopped() {
|
||||
fun onSessionStopped(session: Session) {
|
||||
// noop
|
||||
}
|
||||
}
|
|
@ -53,6 +53,12 @@ object EventType {
|
|||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||
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
|
||||
* - 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_REJECT = "m.call.reject"
|
||||
const val CALL_HANGUP = "m.call.hangup"
|
||||
|
||||
// This type is not processed by the client, just sent to the server
|
||||
const val CALL_REPLACES = "m.call.replaces"
|
||||
|
||||
|
|
|
@ -29,14 +29,19 @@ import java.io.File
|
|||
*/
|
||||
interface FileService {
|
||||
|
||||
enum class FileState {
|
||||
IN_CACHE,
|
||||
DOWNLOADING,
|
||||
UNKNOWN
|
||||
sealed class FileState {
|
||||
/**
|
||||
* The original file is in cache, but the decrypted files can be deleted for security reason.
|
||||
* 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.
|
||||
*/
|
||||
suspend fun downloadFile(fileName: String,
|
||||
|
|
|
@ -66,12 +66,13 @@ interface PushersService {
|
|||
|
||||
/**
|
||||
* 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 appId the application id
|
||||
* @param pushkey the FCM token
|
||||
* @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,
|
||||
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.uploads.UploadsService
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -91,4 +92,9 @@ interface Room :
|
|||
beforeLimit: Int,
|
||||
afterLimit: Int,
|
||||
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.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.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.RoomSummary
|
||||
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.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||
|
||||
|
@ -38,22 +37,19 @@ interface RoomService {
|
|||
/**
|
||||
* Create a room asynchronously
|
||||
*/
|
||||
fun createRoom(createRoomParams: CreateRoomParams,
|
||||
callback: MatrixCallback<String>): Cancelable
|
||||
suspend fun createRoom(createRoomParams: CreateRoomParams): String
|
||||
|
||||
/**
|
||||
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
|
||||
*/
|
||||
fun createDirectRoom(otherUserId: String,
|
||||
callback: MatrixCallback<String>): Cancelable {
|
||||
suspend fun createDirectRoom(otherUserId: String): String {
|
||||
return createRoom(
|
||||
CreateRoomParams()
|
||||
.apply {
|
||||
invitedUserIds.add(otherUserId)
|
||||
setDirectMessage()
|
||||
enableEncryptionIfInvitedUsersSupportIt = true
|
||||
},
|
||||
callback
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -63,10 +59,9 @@ interface RoomService {
|
|||
* @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.
|
||||
*/
|
||||
fun joinRoom(roomIdOrAlias: String,
|
||||
suspend fun joinRoom(roomIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList(),
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
viaServers: List<String> = emptyList())
|
||||
|
||||
/**
|
||||
* Get a room from a roomId
|
||||
|
@ -112,20 +107,18 @@ interface RoomService {
|
|||
* Inform the Matrix SDK that a room is displayed.
|
||||
* 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
|
||||
*/
|
||||
fun markAllAsRead(roomIds: List<String>,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
suspend fun markAllAsRead(roomIds: List<String>)
|
||||
|
||||
/**
|
||||
* Resolve a room alias to a room ID.
|
||||
*/
|
||||
fun getRoomIdByAlias(roomAlias: String,
|
||||
searchOnServer: Boolean,
|
||||
callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable
|
||||
suspend fun getRoomIdByAlias(roomAlias: String,
|
||||
searchOnServer: Boolean): Optional<RoomAliasDescription>
|
||||
|
||||
/**
|
||||
* Delete a room alias
|
||||
|
@ -172,26 +165,28 @@ interface RoomService {
|
|||
/**
|
||||
* 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)
|
||||
* 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
|
||||
*/
|
||||
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
||||
suspend fun peekRoom(roomIdOrAlias: String): PeekResult
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
*/
|
||||
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<PagedList<RoomSummary>>
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
*/
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
|
@ -205,4 +200,12 @@ interface RoomService {
|
|||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(10)
|
||||
.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");
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
|
||||
sealed class GroupListAction : VectorViewModelAction {
|
||||
data class SelectGroup(val groupSummary: GroupSummary) : GroupListAction()
|
||||
enum class RoomSortOrder {
|
||||
NAME,
|
||||
ACTIVITY,
|
||||
NONE
|
||||
}
|
|
@ -16,15 +16,35 @@
|
|||
|
||||
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.RoomCategoryFilter
|
||||
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.RoomType
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||
|
||||
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||
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:
|
||||
* [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 memberships: List<Membership>,
|
||||
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 {
|
||||
|
@ -46,6 +70,10 @@ data class RoomSummaryQueryParams(
|
|||
var memberships: List<Membership> = Membership.all()
|
||||
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
||||
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(
|
||||
roomId = roomId,
|
||||
|
@ -53,7 +81,11 @@ data class RoomSummaryQueryParams(
|
|||
canonicalAlias = canonicalAlias,
|
||||
memberships = memberships,
|
||||
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 org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
interface UpdatableFilterLivePageResult {
|
||||
interface UpdatableLivePageResult {
|
||||
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
|
||||
|
||||
sealed class RoomAliasError : Throwable() {
|
||||
object AliasEmpty : RoomAliasError()
|
||||
object AliasIsBlank : RoomAliasError()
|
||||
object AliasNotAvailable : 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
|
||||
|
||||
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
||||
object CreatedWithTimeout : CreateRoomFailure()
|
||||
data class CreatedWithTimeout(val roomID: String) : CreateRoomFailure()
|
||||
data class CreatedWithFederationFailure(val matrixError: MatrixError) : 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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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
|
||||
|
@ -74,7 +74,7 @@ data class PowerLevelsContent(
|
|||
*/
|
||||
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
|
||||
return copy(
|
||||
users = users.toMutableMap().apply {
|
||||
users = users.orEmpty().toMutableMap().apply {
|
||||
if (powerLevel == null || powerLevel == usersDefault) {
|
||||
remove(userId)
|
||||
} else {
|
||||
|
@ -91,7 +91,7 @@ data class PowerLevelsContent(
|
|||
* @return the level, default to Moderator if the key is not found
|
||||
*/
|
||||
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
|
||||
is String -> value.toInt()
|
||||
is Double -> value.toInt()
|
||||
|
@ -107,3 +107,12 @@ data class PowerLevelsContent(
|
|||
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)
|
||||
enum class GuestAccess {
|
||||
@Json(name = "can_join") CanJoin,
|
||||
@Json(name = "forbidden") Forbidden
|
||||
enum class GuestAccess(val value: String) {
|
||||
@Json(name = "can_join") CanJoin("can_join"),
|
||||
@Json(name = "forbidden") Forbidden("forbidden")
|
||||
}
|
||||
|
|
|
@ -16,35 +16,31 @@
|
|||
|
||||
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
|
||||
*/
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RoomHistoryVisibility {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Json(name = "world_readable") WORLD_READABLE,
|
||||
WORLD_READABLE,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Json(name = "shared") SHARED,
|
||||
SHARED,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Json(name = "invited") INVITED,
|
||||
INVITED,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RoomJoinRules {
|
||||
@Json(name = "public") PUBLIC,
|
||||
@Json(name = "invite") INVITE,
|
||||
@Json(name = "knock") KNOCK,
|
||||
@Json(name = "private") PRIVATE
|
||||
enum class RoomJoinRules(val value: String) {
|
||||
@Json(name = "public") PUBLIC("public"),
|
||||
@Json(name = "invite") INVITE("invite"),
|
||||
@Json(name = "knock") KNOCK("knock"),
|
||||
@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 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.
|
||||
|
@ -26,13 +27,18 @@ import timber.log.Timber
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
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) {
|
||||
"public" -> RoomJoinRules.PUBLIC
|
||||
"invite" -> RoomJoinRules.INVITE
|
||||
"knock" -> RoomJoinRules.KNOCK
|
||||
"private" -> RoomJoinRules.PRIVATE
|
||||
"restricted" -> RoomJoinRules.RESTRICTED
|
||||
else -> {
|
||||
Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
|
||||
null
|
||||
|
|
|
@ -37,6 +37,7 @@ data class RoomSummary constructor(
|
|||
val canonicalAlias: String? = null,
|
||||
val aliases: List<String> = emptyList(),
|
||||
val isDirect: Boolean = false,
|
||||
val directUserId: String? = null,
|
||||
val joinedMembersCount: Int? = 0,
|
||||
val invitedMembersCount: Int? = 0,
|
||||
val latestPreviewableEvent: TimelineEvent? = null,
|
||||
|
@ -48,6 +49,7 @@ data class RoomSummary constructor(
|
|||
val hasUnreadMessages: Boolean = false,
|
||||
val hasUnreadContentMessages: Boolean = false,
|
||||
val hasUnreadOriginalContentMessages: Boolean = false,
|
||||
val unreadCount: Int? = 0,
|
||||
val markedUnread: Boolean = false,
|
||||
val tags: List<RoomTag> = emptyList(),
|
||||
val membership: Membership = Membership.NONE,
|
||||
|
@ -60,7 +62,11 @@ data class RoomSummary constructor(
|
|||
val inviterId: String? = null,
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
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
|
||||
|
|
|
@ -47,7 +47,7 @@ data class RoomThirdPartyInviteContent(
|
|||
/**
|
||||
* 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)
|
||||
|
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,14 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.grouplist
|
||||
package org.matrix.android.sdk.api.session.room.model
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
|
||||
data class GroupListViewState(
|
||||
val asyncGroups: Async<List<GroupSummary>> = Uninitialized,
|
||||
val selectedGroup: GroupSummary? = null
|
||||
) : MvRxState
|
||||
data class SpaceParentInfo(
|
||||
val parentId: String?,
|
||||
val roomSummary: RoomSummary?,
|
||||
val canonical: Boolean?,
|
||||
val viaServers: List<String>
|
||||
)
|
|
@ -18,13 +18,15 @@ package org.matrix.android.sdk.api.session.room.model.create
|
|||
|
||||
import android.net.Uri
|
||||
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.RoomDirectoryVisibility
|
||||
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
|
||||
|
||||
// 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 private visibility will hide the room from the published room list.
|
||||
|
@ -68,6 +70,11 @@ class CreateRoomParams {
|
|||
*/
|
||||
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,
|
||||
* 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
|
||||
*/
|
||||
|
@ -136,7 +154,12 @@ class CreateRoomParams {
|
|||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
}
|
||||
|
||||
var roomVersion: String? = null
|
||||
|
||||
var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null
|
||||
|
||||
companion object {
|
||||
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(
|
||||
@Json(name = "creator") val creator: 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
|
||||
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
sealed class PeekResult {
|
||||
data class Success(
|
||||
val roomId: String,
|
||||
|
@ -24,7 +26,9 @@ sealed class PeekResult {
|
|||
val topic: String?,
|
||||
val avatarUrl: String?,
|
||||
val numJoinedMembers: Int?,
|
||||
val viaServers: List<String>
|
||||
val roomType: String?,
|
||||
val viaServers: List<String>,
|
||||
val someMembers: List<MatrixItem.UserItem>?
|
||||
) : PeekResult()
|
||||
|
||||
data class PeekingNotAllowed(
|
||||
|
@ -34,4 +38,6 @@ sealed class PeekResult {
|
|||
) : PeekResult()
|
||||
|
||||
object UnknownAlias : PeekResult()
|
||||
|
||||
fun isSuccess() = this is Success
|
||||
}
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
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.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.
|
||||
|
@ -31,9 +38,9 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevelValue(userId: String): Int {
|
||||
return powerLevelsContent.users.getOrElse(userId) {
|
||||
powerLevelsContent.usersDefault
|
||||
}
|
||||
return powerLevelsContent.users
|
||||
?.get(userId)
|
||||
?: powerLevelsContent.usersDefaultOrDefault()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +52,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
fun getUserRole(userId: String): Role {
|
||||
val value = getUserPowerLevelValue(userId)
|
||||
// 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 {
|
||||
return if (userId.isNotEmpty()) {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
val minimumPowerLevel = powerLevelsContent.events[eventType]
|
||||
val minimumPowerLevel = powerLevelsContent.events?.get(eventType)
|
||||
?: if (isState) {
|
||||
powerLevelsContent.stateDefault
|
||||
powerLevelsContent.stateDefaultOrDefault()
|
||||
} else {
|
||||
powerLevelsContent.eventsDefault
|
||||
powerLevelsContent.eventsDefaultOrDefault()
|
||||
}
|
||||
powerLevel >= minimumPowerLevel
|
||||
} else false
|
||||
|
@ -76,7 +83,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
*/
|
||||
fun isUserAbleToInvite(userId: String): Boolean {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.redact
|
||||
return powerLevel >= powerLevelsContent.redactOrDefault()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ data class RoomAggregateNotificationCount(
|
|||
val unreadCount: Int,
|
||||
val markedUnreadCount: Int
|
||||
) {
|
||||
val totalCount = notificationCount + highlightCount + markedUnreadCount
|
||||
val totalCount = notificationCount + markedUnreadCount
|
||||
val isHighlight = highlightCount > 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.room.model.RoomMemberSummary
|
||||
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.sender.SenderInfo
|
||||
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 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 {
|
||||
const val Any: String = "*/*"
|
||||
const val OctetStream = "application/octet-stream"
|
||||
const val Apk = "application/vnd.android.package-archive"
|
||||
|
||||
const val Images = "image/*"
|
||||
|
||||
|
|
|
@ -144,7 +144,6 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
return result.fold(
|
||||
{
|
||||
if (it is LoginFlowResult.Success) {
|
||||
// The homeserver exists and up to date, keep the config
|
||||
// Homeserver url may have been changed, if it was a Riot url
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
|
@ -153,7 +152,6 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
|
||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||
}
|
||||
it
|
||||
},
|
||||
{
|
||||
|
@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
val loginFlowResponse = executeRequest(null) {
|
||||
authAPI.getLoginFlows()
|
||||
}
|
||||
return LoginFlowResult.Success(
|
||||
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||
loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl,
|
||||
!versions.isSupportedBySdk()
|
||||
return LoginFlowResult(
|
||||
supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||
ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||
isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl = homeServerUrl,
|
||||
isOutdatedHomeserver = !versions.isSupportedBySdk()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -66,8 +66,8 @@ internal class DefaultRegistrationWizard(
|
|||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override suspend fun createAccount(userName: String,
|
||||
password: String,
|
||||
override suspend fun createAccount(userName: String?,
|
||||
password: String?,
|
||||
initialDeviceDisplayName: String?): RegistrationResult {
|
||||
val params = RegistrationParams(
|
||||
username = userName,
|
||||
|
|
|
@ -44,7 +44,7 @@ data class CryptoDeviceInfo(
|
|||
*/
|
||||
fun fingerprint(): String? {
|
||||
return keys
|
||||
?.takeIf { !deviceId.isBlank() }
|
||||
?.takeIf { deviceId.isNotBlank() }
|
||||
?.get("ed25519:$deviceId")
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ data class CryptoDeviceInfo(
|
|||
*/
|
||||
fun identityKey(): String? {
|
||||
return keys
|
||||
?.takeIf { !deviceId.isBlank() }
|
||||
?.takeIf { deviceId.isNotBlank() }
|
||||
?.get("curve25519:$deviceId")
|
||||
}
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ data class MXDeviceInfo(
|
|||
*/
|
||||
fun fingerprint(): String? {
|
||||
return keys
|
||||
?.takeIf { !deviceId.isBlank() }
|
||||
?.takeIf { deviceId.isNotBlank() }
|
||||
?.get("ed25519:$deviceId")
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ data class MXDeviceInfo(
|
|||
*/
|
||||
fun identityKey(): String? {
|
||||
return keys
|
||||
?.takeIf { !deviceId.isBlank() }
|
||||
?.takeIf { deviceId.isNotBlank() }
|
||||
?.get("curve25519:$deviceId")
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import io.realm.Realm
|
|||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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.model.ChunkEntity
|
||||
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.deleteOnCascade
|
||||
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.task.TaskExecutor
|
||||
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,
|
||||
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
|
||||
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
taskExecutor.executorScope.launch(Dispatchers.Default) {
|
||||
awaitTransaction(realmConfiguration) { realm ->
|
||||
val allRooms = realm.where(RoomEntity::class.java).findAll()
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.database
|
||||
|
||||
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 io.realm.Realm
|
||||
import io.realm.RealmChangeListener
|
||||
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
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 lateinit var results: AtomicReference<RealmResults<T>>
|
||||
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
if (isStarted.compareAndSet(false, true)) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
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)) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,9 @@ import android.os.Looper
|
|||
import androidx.annotation.MainThread
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
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.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.api.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
import kotlin.concurrent.getOrSet
|
||||
|
@ -44,14 +45,14 @@ internal class RealmSessionProvider @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
|
||||
@MainThread
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
realmThreadLocal.getOrSet {
|
||||
Realm.getInstance(monarchy.realmConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun onSessionStopped() {
|
||||
override fun onSessionStopped(session: Session) {
|
||||
realmThreadLocal.get()?.close()
|
||||
realmThreadLocal.remove()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,10 @@ package org.matrix.android.sdk.internal.database
|
|||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
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.internal.database.model.CurrentStateEventEntityFields
|
||||
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.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.RoomTagEntityFields
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -39,10 +45,10 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
companion object {
|
||||
// SC-specific DB changes on top of Element
|
||||
// 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_VERSION = 9L +
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 10L +
|
||||
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 <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
|
||||
if (oldScVersion <= 0) migrateToSc1(realm)
|
||||
if (oldScVersion <= 1) migrateToSc2(realm)
|
||||
}
|
||||
|
||||
// SC Version 1L added markedUnread
|
||||
|
@ -72,6 +80,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
?.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) {
|
||||
|
@ -194,7 +209,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||
|
||||
?.transform { obj ->
|
||||
|
||||
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
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
|
||||
|
||||
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.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
||||
|
@ -49,6 +51,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
topic = roomSummaryEntity.topic ?: "",
|
||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||
isDirect = roomSummaryEntity.isDirect,
|
||||
directUserId = roomSummaryEntity.directUserId,
|
||||
latestPreviewableEvent = latestEvent,
|
||||
latestPreviewableContentEvent = latestContentEvent,
|
||||
latestPreviewableOriginalContentEvent = latestOriginalContentEvent,
|
||||
|
@ -74,7 +77,32 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||
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) {
|
||||
membersLoadStatusStr = value.name
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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() {
|
||||
|
||||
var displayName: String? = ""
|
||||
|
@ -103,6 +106,17 @@ internal open class RoomSummaryEntity(
|
|||
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
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
|
@ -229,6 +243,16 @@ internal open class RoomSummaryEntity(
|
|||
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
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
||||
|
@ -269,6 +293,5 @@ internal open class RoomSummaryEntity(
|
|||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ import io.realm.annotations.RealmModule
|
|||
CurrentStateEventEntity::class,
|
||||
UserAccountDataEntity::class,
|
||||
ScalarTokenEntity::class,
|
||||
WellknownIntegrationManagerConfigEntity::class
|
||||
WellknownIntegrationManagerConfigEntity::class,
|
||||
SpaceChildSummaryEntity::class,
|
||||
SpaceParentSummaryEntity::class
|
||||
])
|
||||
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) {
|
||||
is IOException -> Failure.NetworkConnection(exception)
|
||||
is Failure.ServerError,
|
||||
is Failure.OtherServerError -> exception
|
||||
is CancellationException -> Failure.Cancelled(exception)
|
||||
is Failure.OtherServerError,
|
||||
is CancellationException -> 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
|
||||
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import io.realm.Case
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmQuery
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import timber.log.Timber
|
||||
|
||||
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||
|
|
|
@ -219,7 +219,7 @@ internal class DefaultFileService @Inject constructor(
|
|||
fileName: String,
|
||||
mimeType: String?,
|
||||
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(
|
||||
|
@ -256,12 +256,17 @@ internal class DefaultFileService @Inject constructor(
|
|||
fileName: String,
|
||||
mimeType: String?,
|
||||
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
|
||||
mxcUrl ?: return FileService.FileState.UNKNOWN
|
||||
if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE
|
||||
mxcUrl ?: return FileService.FileState.Unknown
|
||||
val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null)
|
||||
if (files.file.exists()) {
|
||||
return FileService.FileState.InCache(
|
||||
decryptedFileInCache = files.getClearFile().exists()
|
||||
)
|
||||
}
|
||||
val isDownloading = synchronized(ongoing) {
|
||||
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 dagger.Lazy
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.federation.FederationService
|
||||
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.SessionLifecycleObserver
|
||||
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.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.group.GroupService
|
||||
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.media.MediaService
|
||||
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.SharedSecretStorageService
|
||||
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.terms.TermsService
|
||||
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
|
||||
|
@ -120,6 +124,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val integrationManagerService: IntegrationManagerService,
|
||||
private val thirdPartyService: Lazy<ThirdPartyService>,
|
||||
private val callSignalingService: Lazy<CallSignalingService>,
|
||||
private val spaceService: Lazy<SpaceService>,
|
||||
@UnauthenticatedWithCertificate
|
||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
||||
) : Session,
|
||||
|
@ -159,7 +164,12 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = true
|
||||
cryptoService.get().ensureDevice()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach { it.onSessionStarted() }
|
||||
lifecycleObservers.forEach {
|
||||
it.onSessionStarted(this)
|
||||
}
|
||||
sessionListeners.dispatch {
|
||||
it.onSessionStarted(this)
|
||||
}
|
||||
}
|
||||
globalErrorHandler.listener = this
|
||||
}
|
||||
|
@ -200,7 +210,10 @@ internal class DefaultSession @Inject constructor(
|
|||
stopSync()
|
||||
// timelineEventDecryptor.destroy()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach { it.onSessionStopped() }
|
||||
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
||||
sessionListeners.dispatch {
|
||||
it.onSessionStopped(this)
|
||||
}
|
||||
}
|
||||
cryptoService.get().close()
|
||||
isOpen = false
|
||||
|
@ -225,14 +238,23 @@ internal class DefaultSession @Inject constructor(
|
|||
stopSync()
|
||||
stopAnyBackgroundSync()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach { it.onClearCache() }
|
||||
lifecycleObservers.forEach {
|
||||
it.onClearCache(this)
|
||||
}
|
||||
sessionListeners.dispatch {
|
||||
it.onClearCache(this)
|
||||
}
|
||||
}
|
||||
withContext(NonCancellable) {
|
||||
cacheService.get().clearCache()
|
||||
}
|
||||
workManagerProvider.cancelAllWorks()
|
||||
}
|
||||
|
||||
override fun onGlobalError(globalError: GlobalError) {
|
||||
sessionListeners.dispatchGlobalError(globalError)
|
||||
sessionListeners.dispatch {
|
||||
it.onGlobalError(this, globalError)
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentUrlResolver() = contentUrlResolver
|
||||
|
@ -265,6 +287,8 @@ internal class DefaultSession @Inject constructor(
|
|||
|
||||
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
|
||||
|
||||
override fun spaceService(): SpaceService = spaceService.get()
|
||||
|
||||
override fun getOkHttpClient(): OkHttpClient {
|
||||
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.search.SearchModule
|
||||
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.SyncTask
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
|
@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
|||
FederationModule::class,
|
||||
CallModule::class,
|
||||
SearchModule::class,
|
||||
ThirdPartyModule::class
|
||||
ThirdPartyModule::class,
|
||||
SpaceModule::class
|
||||
]
|
||||
)
|
||||
@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
|
||||
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
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) {
|
||||
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.crypto.MXCryptoConfig
|
||||
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.events.EventService
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||
|
@ -343,6 +344,10 @@ internal abstract class SessionModule {
|
|||
@IntoSet
|
||||
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
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.extensions.observeNotNull
|
||||
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.identity.data.IdentityStore
|
||||
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 okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
@ -86,7 +87,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
|
||||
private val listeners = mutableSetOf<IdentityServiceListener>()
|
||||
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
// Observe the account data change
|
||||
accountDataDataSource
|
||||
|
@ -111,7 +112,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSessionStopped() {
|
||||
override fun onSessionStopped(session: Session) {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.LifecycleRegistry
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
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.integrationmanager.IntegrationManagerConfig
|
||||
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.di.SessionDatabase
|
||||
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.api.session.accountdata.UserAccountDataTypes
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
|
@ -77,7 +78,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
currentConfigs.add(defaultConfig)
|
||||
}
|
||||
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
observeWellknownConfig()
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.members.MembershipService
|
||||
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.notification.RoomPushRuleService
|
||||
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.uploads.UploadsService
|
||||
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.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
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.search.SearchTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import java.security.InvalidParameterException
|
||||
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.paging.PagedList
|
||||
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.room.Room
|
||||
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.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.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
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.toOptional
|
||||
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.summary.RoomSummaryDataSource
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -67,16 +65,11 @@ internal class DefaultRoomService @Inject constructor(
|
|||
private val peekRoomTask: PeekRoomTask,
|
||||
private val roomGetter: RoomGetter,
|
||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
|
||||
) : RoomService {
|
||||
|
||||
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
|
||||
return createRoomTask
|
||||
.configureWith(createRoomParams) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
|
||||
return createRoomTask.executeRetry(createRoomParams, 3)
|
||||
}
|
||||
|
||||
override fun getRoom(roomId: String): Room? {
|
||||
|
@ -99,14 +92,14 @@ internal class DefaultRoomService @Inject constructor(
|
|||
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>> {
|
||||
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||
}
|
||||
|
||||
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
||||
: UpdatableFilterLivePageResult {
|
||||
return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
|
||||
: UpdatableLivePageResult {
|
||||
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||
}
|
||||
|
||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams, preferenceProvider: RoomSummary.RoomSummaryPreferenceProvider): RoomAggregateNotificationCount {
|
||||
|
@ -121,34 +114,20 @@ internal class DefaultRoomService @Inject constructor(
|
|||
return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
|
||||
}
|
||||
|
||||
override fun onRoomDisplayed(roomId: String): Cancelable {
|
||||
return updateBreadcrumbsTask
|
||||
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun onRoomDisplayed(roomId: String) {
|
||||
updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
|
||||
}
|
||||
|
||||
override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return joinRoomTask
|
||||
.configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
|
||||
joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
|
||||
}
|
||||
|
||||
override fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return markAllRoomsReadTask
|
||||
.configureWith(MarkAllRoomsReadTask.Params(roomIds)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun markAllAsRead(roomIds: List<String>) {
|
||||
markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
|
||||
}
|
||||
|
||||
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable {
|
||||
return roomIdByAliasTask
|
||||
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional<RoomAliasDescription> {
|
||||
return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer))
|
||||
}
|
||||
|
||||
override suspend fun deleteRoomAlias(roomAlias: String) {
|
||||
|
@ -179,19 +158,25 @@ internal class DefaultRoomService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) {
|
||||
resolveRoomStateTask
|
||||
.configureWith(ResolveRoomStateTask.Params(roomId)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun getRoomState(roomId: String): List<Event> {
|
||||
return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
|
||||
}
|
||||
|
||||
override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) {
|
||||
peekRoomTask
|
||||
.configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
|
||||
this.callback = callback
|
||||
override suspend fun peekRoom(roomIdOrAlias: String): PeekResult {
|
||||
return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias))
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
|
||||
override fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List<Membership>): List<RoomSummary> {
|
||||
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.room.RoomDirectoryService
|
||||
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.SessionScope
|
||||
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.uploads.DefaultGetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -137,6 +139,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
||||
|
||||
@Binds
|
||||
abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService
|
||||
|
||||
@Binds
|
||||
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)
|
||||
suspend fun check(aliasLocalPart: String?) {
|
||||
if (aliasLocalPart.isNullOrEmpty()) {
|
||||
throw RoomAliasError.AliasEmpty
|
||||
// don't check empty or not provided alias
|
||||
return
|
||||
}
|
||||
if (aliasLocalPart.isBlank()) {
|
||||
throw RoomAliasError.AliasIsBlank
|
||||
}
|
||||
// Check alias availability
|
||||
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
|
||||
*/
|
||||
@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.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||
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.util.MimeTypes
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
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.UserId
|
||||
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.identity.EnsureIdentityTokenTask
|
||||
|
@ -43,6 +49,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
private val deviceListManager: DeviceListManager,
|
||||
private val identityStore: IdentityStore,
|
||||
private val fileUploader: FileUploader,
|
||||
@UserId
|
||||
private val userId: String,
|
||||
@AuthenticatedIdentity
|
||||
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(
|
||||
buildEncryptionWithAlgorithmEvent(params),
|
||||
buildHistoryVisibilityEvent(params),
|
||||
buildAvatarEvent(params)
|
||||
buildAvatarEvent(params),
|
||||
buildGuestAccess(params),
|
||||
buildJoinRulesRestricted(params)
|
||||
)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
|
||||
|
@ -80,13 +95,15 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
roomAliasName = params.roomAliasName,
|
||||
name = params.name,
|
||||
topic = params.topic,
|
||||
invitedUserIds = params.invitedUserIds,
|
||||
invitedUserIds = params.invitedUserIds.filter { it != userId },
|
||||
invite3pids = invite3pids,
|
||||
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
|
||||
initialStates = initialStates,
|
||||
preset = params.preset,
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
|
|||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
throw CreateRoomFailure.CreatedWithTimeout
|
||||
throw CreateRoomFailure.CreatedWithTimeout(roomId)
|
||||
}
|
||||
|
||||
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.RoomCanonicalAliasContent
|
||||
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.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.PublicRoomsParams
|
||||
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.directory.GetPublicRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
||||
|
@ -100,7 +103,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||
name = publicRepoResult.name,
|
||||
topic = publicRepoResult.topic,
|
||||
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 }
|
||||
|
||||
// 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 }
|
||||
|
||||
val memberCount = membersEvent
|
||||
.distinctBy { it.stateKey }
|
||||
.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(
|
||||
roomId = roomId,
|
||||
alias = alias,
|
||||
|
@ -137,7 +156,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
|
|||
name = name,
|
||||
topic = topic,
|
||||
numJoinedMembers = memberCount,
|
||||
viaServers = serverList
|
||||
roomType = roomType,
|
||||
viaServers = serverList,
|
||||
someMembers = someMembers
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
// 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.util.Cancelable
|
||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.api.session.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.MatrixError
|
||||
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.events.model.Event
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
@ -72,7 +73,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
|
|||
*/
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -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.MatrixError
|
||||
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.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
|
@ -64,11 +65,11 @@ internal class EventSenderProcessorThread @Inject constructor(
|
|||
memento.unTrack(task)
|
||||
}
|
||||
|
||||
override fun onSessionStarted() {
|
||||
override fun onSessionStarted(session: Session) {
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onSessionStopped() {
|
||||
override fun onSessionStopped(session: Session) {
|
||||
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.Optional
|
||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||
import java.lang.UnsupportedOperationException
|
||||
|
||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val stateEventDataSource: StateEventDataSource,
|
||||
|
@ -73,7 +74,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
|||
eventType = eventType,
|
||||
body = body.toSafeJson(eventType)
|
||||
)
|
||||
sendStateTask.execute(params)
|
||||
sendStateTask.executeRetry(params, 3)
|
||||
}
|
||||
|
||||
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?) {
|
||||
if (joinRules != null) {
|
||||
if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
|
||||
sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
||||
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.toModel
|
||||
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
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SerializablePowerLevelsContent(
|
||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
||||
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
||||
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
||||
@Json(name = "ban") val ban: Int?,
|
||||
@Json(name = "kick") val kick: Int?,
|
||||
@Json(name = "invite") val invite: Int?,
|
||||
@Json(name = "redact") val redact: Int?,
|
||||
@Json(name = "events_default") val eventsDefault: Int?,
|
||||
@Json(name = "events") val events: Map<String, Int>?,
|
||||
@Json(name = "users_default") val usersDefault: Int?,
|
||||
@Json(name = "users") val users: Map<String, Int>?,
|
||||
@Json(name = "state_default") val stateDefault: Int?,
|
||||
// `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 {
|
||||
|
@ -52,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
|
|||
usersDefault = content.usersDefault,
|
||||
users = content.users,
|
||||
stateDefault = content.stateDefault,
|
||||
notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
|
||||
notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
|
||||
)
|
||||
}
|
||||
?.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 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.
|
||||
|
@ -17,6 +18,7 @@
|
|||
package org.matrix.android.sdk.internal.session.room.summary
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
|
@ -24,12 +26,21 @@ import com.zhuinden.monarchy.Monarchy
|
|||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
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.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.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.RoomType
|
||||
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.space.SpaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
|
||||
|
@ -80,11 +91,60 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
|
||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ roomSummariesQuery(it, queryParams) },
|
||||
{
|
||||
roomSummariesQuery(it, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
},
|
||||
{ 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> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ breadcrumbsQuery(it, queryParams) },
|
||||
|
@ -106,10 +166,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
}
|
||||
|
||||
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> {
|
||||
pagedListConfig: PagedList.Config,
|
||||
sortOrder: RoomSortOrder): LiveData<PagedList<RoomSummary>> {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
roomSummariesQuery(realm, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
roomSummariesQuery(realm, queryParams).process(sortOrder)
|
||||
}
|
||||
val dataSourceFactory = realmDataSourceFactory.map {
|
||||
roomSummaryMapper.map(it)
|
||||
|
@ -120,30 +180,48 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
)
|
||||
}
|
||||
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
|
||||
fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config,
|
||||
sortOrder: RoomSortOrder): UpdatableLivePageResult {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
roomSummariesQuery(realm, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
roomSummariesQuery(realm, queryParams).process(sortOrder)
|
||||
}
|
||||
val dataSourceFactory = realmDataSourceFactory.map {
|
||||
roomSummaryMapper.map(it)
|
||||
}
|
||||
|
||||
val boundaries = MutableLiveData(ResultBoundaries())
|
||||
|
||||
val mapped = monarchy.findAllPagedWithChanges(
|
||||
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 fun updateQuery(queryParams: RoomSummaryQueryParams) {
|
||||
override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
|
||||
realmDataSourceFactory.updateQuery {
|
||||
roomSummariesQuery(it, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
override val liveBoundaries: LiveData<ResultBoundaries>
|
||||
get() = boundaries
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +275,162 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
|||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
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.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.RoomNameContent
|
||||
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.internal.crypto.EventDecryptor
|
||||
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.EventEntity
|
||||
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.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.query.findAllInRoomWithSendStates
|
||||
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.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.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.membership.RoomDisplayNameResolver
|
||||
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.RoomSyncUnreadNotifications
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
internal class RoomSummaryUpdater @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
private val roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||
private val roomAvatarResolver: RoomAvatarResolver,
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val crossSigningService: DefaultCrossSigningService) {
|
||||
private val crossSigningService: DefaultCrossSigningService,
|
||||
private val stateEventDataSource: StateEventDataSource) {
|
||||
|
||||
fun update(realm: Realm,
|
||||
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 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 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
|
||||
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.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)
|
||||
|
||||
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
|
||||
if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) {
|
||||
handleReachEnd(realm, roomId, direction, currentChunk)
|
||||
} else {
|
||||
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…
Reference in a new issue