Merge branch 'develop' into feature/room_profile
This commit is contained in:
commit
f18ec8d021
19
CHANGES.md
19
CHANGES.md
@ -5,13 +5,26 @@ Features ✨:
|
|||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
- The initial sync is now handled by a foreground service
|
||||||
|
- Render aliases and canonical alias change in the timeline
|
||||||
|
- Fix autocompletion issues and add support for rooms and groups
|
||||||
|
- Introduce developer mode in the settings (#745, #796)
|
||||||
|
- Improve devices list screen
|
||||||
|
- Add settings for rageshake sensibility
|
||||||
|
- Fix autocompletion issues and add support for rooms, groups, and emoji (#780)
|
||||||
|
- Show skip to bottom FAB while scrolling down (#752)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
- Change the way RiotX identifies a session to allow the SDK to support several sessions with the same user (#800)
|
||||||
|
- Exclude play-services-oss-licenses library from F-Droid build (#814)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
-
|
- Fix crash when opening room creation screen from the room filtering screen
|
||||||
|
- Fix avatar image disappearing (#777)
|
||||||
|
- Fix read marker banner when permalink
|
||||||
|
- Fix joining upgraded rooms (#697)
|
||||||
|
- Fix matrix.org room directory not being browsable (#807)
|
||||||
|
- Hide non working settings (#751)
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
|
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
|
||||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
@ -31,18 +34,22 @@ class RxRoom(private val room: Room) {
|
|||||||
|
|
||||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
return room.getRoomSummaryLive().asObservable()
|
return room.getRoomSummaryLive().asObservable()
|
||||||
|
.startWith(room.roomSummary().toOptional())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
|
||||||
return room.getRoomMemberIdsLive().asObservable()
|
return room.getRoomMembersLive(queryParams).asObservable()
|
||||||
|
.startWith(room.getRoomMembers(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||||
return room.getEventSummaryLive(eventId).asObservable()
|
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||||
|
.startWith(room.getEventAnnotationsSummary(eventId).toOptional())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||||
return room.getTimeLineEventLive(eventId).asObservable()
|
return room.getTimeLineEventLive(eventId).asObservable()
|
||||||
|
.startWith(room.getTimeLineEvent(eventId).toOptional())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveReadMarker(): Observable<Optional<String>> {
|
fun liveReadMarker(): Observable<Optional<String>> {
|
||||||
|
@ -18,8 +18,10 @@ package im.vector.matrix.rx
|
|||||||
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
|
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
@ -30,40 +32,43 @@ import io.reactivex.Single
|
|||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.liveRoomSummaries().asObservable()
|
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
|
.startWith(session.getRoomSummaries(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
return session.liveGroupSummaries().asObservable()
|
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||||
|
.startWith(session.getGroupSummaries(queryParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||||
return session.liveBreadcrumbs().asObservable()
|
return session.getBreadcrumbsLive().asObservable()
|
||||||
|
.startWith(session.getBreadcrumbs())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
return session.syncState().asObservable()
|
return session.getSyncStateLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
return session.livePushers().asObservable()
|
return session.getPushersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||||
return session.liveUser(userId).asObservable().distinctUntilChanged()
|
return session.getUserLive(userId).asObservable().distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
fun liveUsers(): Observable<List<User>> {
|
||||||
return session.liveUsers().asObservable()
|
return session.getUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveIgnoredUsers(): Observable<List<User>> {
|
fun liveIgnoredUsers(): Observable<List<User>> {
|
||||||
return session.liveIgnoredUsers().asObservable()
|
return session.getIgnoredUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
||||||
return session.livePagedUsers(filter).asObservable()
|
return session.getPagedUsersLive(filter).asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||||
|
@ -10,7 +10,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
classpath "io.realm:realm-gradle-plugin:6.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,6 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0-beta05"
|
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||||
@ -119,14 +118,14 @@ dependencies {
|
|||||||
implementation "ru.noties.markwon:core:$markwon_version"
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'androidx.exifinterface:exifinterface:1.1.0'
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"
|
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
@ -19,4 +19,4 @@ package im.vector.matrix.android
|
|||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
|
||||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.matrix.android.auth
|
|
||||||
|
|
||||||
import androidx.test.annotation.UiThreadTest
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import androidx.test.rule.GrantPermissionRule
|
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
|
||||||
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
|
||||||
import okreplay.*
|
|
||||||
import org.junit.ClassRule
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
internal class AuthenticationServiceTest : InstrumentedTest {
|
|
||||||
|
|
||||||
lateinit var authenticationService: AuthenticationService
|
|
||||||
lateinit var okReplayInterceptor: OkReplayInterceptor
|
|
||||||
|
|
||||||
private val okReplayConfig = OkReplayConfig.Builder()
|
|
||||||
.tapeRoot(AndroidTapeRoot(
|
|
||||||
context(), javaClass))
|
|
||||||
.defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
|
|
||||||
.sslEnabled(true)
|
|
||||||
.interceptor(okReplayInterceptor)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@UiThreadTest
|
|
||||||
@OkReplay(tape = "auth", mode = TapeMode.READ_WRITE)
|
|
||||||
fun auth() {
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@ClassRule
|
|
||||||
@JvmField
|
|
||||||
val grantExternalStoragePermissionRule: GrantPermissionRule =
|
|
||||||
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,20 +16,31 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
import io.realm.Realm
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
import org.matrix.olm.OlmAccount
|
import org.matrix.olm.OlmAccount
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import org.matrix.olm.OlmSession
|
import org.matrix.olm.OlmSession
|
||||||
|
|
||||||
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||||
|
|
||||||
class CryptoStoreTest {
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class CryptoStoreTest : InstrumentedTest {
|
||||||
|
|
||||||
private val cryptoStoreHelper = CryptoStoreHelper()
|
private val cryptoStoreHelper = CryptoStoreHelper()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
Realm.init(context())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_metadata_realm_ok() {
|
fun test_metadata_realm_ok() {
|
||||||
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||||
|
@ -19,8 +19,12 @@ package im.vector.matrix.android.session.room.timeline
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
|
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||||
|
import im.vector.matrix.android.internal.database.helper.merge
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||||
@ -28,7 +32,6 @@ import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeR
|
|||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import org.amshove.kluent.shouldBeFalse
|
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.amshove.kluent.shouldEqual
|
import org.amshove.kluent.shouldEqual
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -43,7 +46,11 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
Realm.init(context())
|
Realm.init(context())
|
||||||
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
|
val testConfig = RealmConfiguration.Builder()
|
||||||
|
.inMemory()
|
||||||
|
.name("test-realm")
|
||||||
|
.modules(SessionRealmModule())
|
||||||
|
.build()
|
||||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
|
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,30 +148,6 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
|
|
||||||
monarchy.runTransactionSync { realm ->
|
|
||||||
val chunk1: ChunkEntity = realm.createObject()
|
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
|
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
|
||||||
chunk1.isUnlinked().shouldBeFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
|
|
||||||
monarchy.runTransactionSync { realm ->
|
|
||||||
val chunk1: ChunkEntity = realm.createObject()
|
|
||||||
val chunk2: ChunkEntity = realm.createObject()
|
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
|
||||||
chunk1.isUnlinked().shouldBeTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
@ -172,8 +155,8 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val prevToken = "prev_token"
|
val prevToken = "prev_token"
|
||||||
chunk1.prevToken = prevToken
|
chunk1.prevToken = prevToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||||
chunk1.prevToken shouldEqual prevToken
|
chunk1.prevToken shouldEqual prevToken
|
||||||
}
|
}
|
||||||
@ -186,10 +169,19 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val nextToken = "next_token"
|
val nextToken = "next_token"
|
||||||
chunk1.nextToken = nextToken
|
chunk1.nextToken = nextToken
|
||||||
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.nextToken shouldEqual nextToken
|
chunk1.nextToken shouldEqual nextToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ChunkEntity.addAll(roomId: String,
|
||||||
|
events: List<Event>,
|
||||||
|
direction: PaginationDirection,
|
||||||
|
stateIndexOffset: Int = 0) {
|
||||||
|
events.forEach { event ->
|
||||||
|
add(roomId, event, direction, stateIndexOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
@ -25,12 +24,6 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.internal.database.helper.addAll
|
|
||||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
|
||||||
import io.realm.kotlin.createObject
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
object RoomDataHelper {
|
object RoomDataHelper {
|
||||||
@ -73,19 +66,4 @@ object RoomDataHelper {
|
|||||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
|
||||||
monarchy.runTransactionSync { realm ->
|
|
||||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
|
||||||
roomEntity.membership = Membership.JOIN
|
|
||||||
val eventList = createFakeListOfEvents(10)
|
|
||||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
|
||||||
nextToken = null
|
|
||||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
|
||||||
isLastForward = true
|
|
||||||
}
|
|
||||||
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
|
||||||
roomEntity.addOrUpdate(chunkEntity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest {
|
|||||||
// val latch = CountDownLatch(2)
|
// val latch = CountDownLatch(2)
|
||||||
// var timelineEvents: List<TimelineEvent> = emptyList()
|
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||||
// timeline.listener = object : Timeline.Listener {
|
// timeline.listener = object : Timeline.Listener {
|
||||||
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
// override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
// if (snapshot.isNotEmpty()) {
|
// if (snapshot.isNotEmpty()) {
|
||||||
// if (initialLoad == 0) {
|
// if (initialLoad == 0) {
|
||||||
// initialLoad = snapshot.size
|
// initialLoad = snapshot.size
|
||||||
|
@ -28,6 +28,12 @@ fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
|||||||
?.chunked(4)
|
?.chunked(4)
|
||||||
?.joinToString(separator = " ")
|
?.joinToString(separator = " ")
|
||||||
|
|
||||||
fun MutableList<DeviceInfo>.sortByLastSeen() {
|
/* ==========================================================================================
|
||||||
sortWith(DatedObjectComparators.descComparator)
|
* DeviceInfo
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
fun List<DeviceInfo>.sortByLastSeen(): List<DeviceInfo> {
|
||||||
|
val list = toMutableList()
|
||||||
|
list.sortWith(DatedObjectComparators.descComparator)
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.core.error
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
fun Throwable.is401(): Boolean {
|
fun Throwable.is401() =
|
||||||
return (this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
this is Failure.ServerError
|
||||||
&& error.code == MatrixError.M_UNAUTHORIZED)
|
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||||
}
|
&& error.code == MatrixError.M_UNAUTHORIZED
|
||||||
|
|
||||||
|
fun Throwable.isTokenError() =
|
||||||
|
this is Failure.ServerError
|
||||||
|
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.api.permalinks
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatrixLinkify take a piece of text and turns all of the
|
* MatrixLinkify take a piece of text and turns all of the
|
||||||
@ -30,7 +29,13 @@ object MatrixLinkify {
|
|||||||
*
|
*
|
||||||
* @param spannable the text in which the matrix items has to be clickable.
|
* @param spannable the text in which the matrix items has to be clickable.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||||
|
/**
|
||||||
|
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
|
||||||
|
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
||||||
|
*/
|
||||||
|
/*
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if (spannable.isEmpty()) {
|
if (spannable.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
@ -50,5 +55,7 @@ object MatrixLinkify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasMatch
|
return hasMatch
|
||||||
|
*/
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,23 +56,23 @@ object PermalinkParser {
|
|||||||
|
|
||||||
val identifier = params.getOrNull(0)
|
val identifier = params.getOrNull(0)
|
||||||
val extraParameter = params.getOrNull(1)
|
val extraParameter = params.getOrNull(1)
|
||||||
if (identifier.isNullOrEmpty()) {
|
|
||||||
return PermalinkData.FallbackLink(uri)
|
|
||||||
}
|
|
||||||
return when {
|
return when {
|
||||||
|
identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
|
||||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||||
MatrixPatterns.isRoomId(identifier) -> {
|
MatrixPatterns.isRoomId(identifier) -> {
|
||||||
val eventId = extraParameter.takeIf {
|
PermalinkData.RoomLink(
|
||||||
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
roomIdOrAlias = identifier,
|
||||||
}
|
isRoomAlias = false,
|
||||||
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId)
|
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
MatrixPatterns.isRoomAlias(identifier) -> {
|
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||||
val eventId = extraParameter.takeIf {
|
PermalinkData.RoomLink(
|
||||||
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
roomIdOrAlias = identifier,
|
||||||
}
|
isRoomAlias = true,
|
||||||
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId)
|
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> PermalinkData.FallbackLink(uri)
|
else -> PermalinkData.FallbackLink(uri)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic query language. All these cases are mutually exclusive.
|
||||||
|
*/
|
||||||
|
sealed class QueryStringValue {
|
||||||
|
object NoCondition : QueryStringValue()
|
||||||
|
object IsNull : QueryStringValue()
|
||||||
|
object IsNotNull : QueryStringValue()
|
||||||
|
object IsEmpty : QueryStringValue()
|
||||||
|
object IsNotEmpty : QueryStringValue()
|
||||||
|
data class Equals(val string: String, val case: Case) : QueryStringValue()
|
||||||
|
data class Contains(val string: String, val case: Case) : QueryStringValue()
|
||||||
|
|
||||||
|
enum class Case {
|
||||||
|
SENSITIVE,
|
||||||
|
INSENSITIVE
|
||||||
|
}
|
||||||
|
}
|
@ -107,7 +107,12 @@ interface Session :
|
|||||||
* This method allows to listen the sync state.
|
* This method allows to listen the sync state.
|
||||||
* @return a [LiveData] of [SyncState].
|
* @return a [LiveData] of [SyncState].
|
||||||
*/
|
*/
|
||||||
fun syncState(): LiveData<SyncState>
|
fun getSyncStateLive(): LiveData<SyncState>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods return true if an initial sync has been processed
|
||||||
|
*/
|
||||||
|
fun hasAlreadySynced(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method allow to close a session. It does stop some services.
|
* This method allow to close a session. It does stop some services.
|
||||||
|
@ -24,7 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
interface CacheService {
|
interface CacheService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again
|
* Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
|
||||||
*/
|
*/
|
||||||
fun clearCache(callback: MatrixCallback<Unit>)
|
fun clearCache(callback: MatrixCallback<Unit>)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
|||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
|
||||||
@ -89,6 +90,8 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
fun getDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||||
|
|
||||||
|
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||||
|
|
||||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||||
|
|
||||||
fun isRoomEncrypted(roomId: String): Boolean
|
fun isRoomEncrypted(roomId: String): Boolean
|
||||||
|
@ -50,10 +50,10 @@ object EventType {
|
|||||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||||
const val STATE_ROOM_ALIASES = "m.room.aliases"
|
const val STATE_ROOM_ALIASES = "m.room.aliases"
|
||||||
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
|
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
|
||||||
const val STATE_CANONICAL_ALIAS = "m.room.canonical_alias"
|
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"
|
||||||
const val STATE_HISTORY_VISIBILITY = "m.room.history_visibility"
|
const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility"
|
||||||
const val STATE_RELATED_GROUPS = "m.room.related_groups"
|
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
||||||
const val STATE_PINNED_EVENT = "m.room.pinned_events"
|
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
|
|
||||||
@ -86,10 +86,12 @@ object EventType {
|
|||||||
STATE_ROOM_JOIN_RULES,
|
STATE_ROOM_JOIN_RULES,
|
||||||
STATE_ROOM_GUEST_ACCESS,
|
STATE_ROOM_GUEST_ACCESS,
|
||||||
STATE_ROOM_POWER_LEVELS,
|
STATE_ROOM_POWER_LEVELS,
|
||||||
|
STATE_ROOM_ALIASES,
|
||||||
STATE_ROOM_TOMBSTONE,
|
STATE_ROOM_TOMBSTONE,
|
||||||
STATE_HISTORY_VISIBILITY,
|
STATE_ROOM_CANONICAL_ALIAS,
|
||||||
STATE_RELATED_GROUPS,
|
STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
STATE_PINNED_EVENT
|
STATE_ROOM_RELATED_GROUPS,
|
||||||
|
STATE_ROOM_PINNED_EVENT
|
||||||
)
|
)
|
||||||
|
|
||||||
fun isStateEvent(type: String): Boolean {
|
fun isStateEvent(type: String): Boolean {
|
||||||
|
@ -31,9 +31,22 @@ interface GroupService {
|
|||||||
*/
|
*/
|
||||||
fun getGroup(groupId: String): Group?
|
fun getGroup(groupId: String): Group?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a groupSummary from a groupId
|
||||||
|
* @param groupId the groupId to look for.
|
||||||
|
* @return the groupSummary with groupId or null
|
||||||
|
*/
|
||||||
|
fun getGroupSummary(groupId: String): GroupSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of group summaries. This list is a snapshot of the data.
|
||||||
|
* @return the list of [GroupSummary]
|
||||||
|
*/
|
||||||
|
fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of [GroupSummary]
|
* @return the [LiveData] of [GroupSummary]
|
||||||
*/
|
*/
|
||||||
fun liveGroupSummaries(): LiveData<List<GroupSummary>>
|
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.group
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
|
||||||
|
fun groupSummaryQueryParams(init: (GroupSummaryQueryParams.Builder.() -> Unit) = {}): GroupSummaryQueryParams {
|
||||||
|
return GroupSummaryQueryParams.Builder().apply(init).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to filter group summaries
|
||||||
|
*/
|
||||||
|
data class GroupSummaryQueryParams(
|
||||||
|
val displayName: QueryStringValue,
|
||||||
|
val memberships: List<Membership>
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
|
||||||
|
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||||
|
var memberships: List<Membership> = Membership.all()
|
||||||
|
|
||||||
|
fun build() = GroupSummaryQueryParams(
|
||||||
|
displayName = displayName,
|
||||||
|
memberships = memberships
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ interface PushersService {
|
|||||||
const val EVENT_ID_ONLY = "event_id_only"
|
const val EVENT_ID_ONLY = "event_id_only"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): LiveData<List<Pusher>>
|
fun getPushersLive(): LiveData<List<Pusher>>
|
||||||
|
|
||||||
fun pushers() : List<Pusher>
|
fun pushers() : List<Pusher>
|
||||||
}
|
}
|
||||||
|
@ -56,5 +56,8 @@ interface Room :
|
|||||||
*/
|
*/
|
||||||
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A current snapshot of [RoomSummary] associated with the room
|
||||||
|
*/
|
||||||
fun roomSummary(): RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
}
|
}
|
||||||
|
@ -53,16 +53,35 @@ interface RoomService {
|
|||||||
fun getRoom(roomId: String): Room?
|
fun getRoom(roomId: String): Room?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a roomSummary from a roomId or a room alias
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @param roomIdOrAlias the roomId or the alias of a room to look for.
|
||||||
|
* @return a matching room summary or null
|
||||||
*/
|
*/
|
||||||
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a snapshot list of room summaries.
|
||||||
|
* @return the immutable list of [RoomSummary]
|
||||||
|
*/
|
||||||
|
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||||
|
* @return the [LiveData] of List[RoomSummary]
|
||||||
|
*/
|
||||||
|
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a snapshot list of Breadcrumbs
|
||||||
|
* @return the immutable list of [RoomSummary]
|
||||||
|
*/
|
||||||
|
fun getBreadcrumbs(): List<RoomSummary>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of Breadcrumbs
|
* Get a live list of Breadcrumbs
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @return the [LiveData] of [RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun liveBreadcrumbs(): LiveData<List<RoomSummary>>
|
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inform the Matrix SDK that a room is displayed.
|
* Inform the Matrix SDK that a room is displayed.
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
|
||||||
|
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||||
|
return RoomSummaryQueryParams.Builder().apply(init).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to filter room summaries to use with:
|
||||||
|
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
||||||
|
*/
|
||||||
|
data class RoomSummaryQueryParams(
|
||||||
|
val displayName: QueryStringValue,
|
||||||
|
val canonicalAlias: QueryStringValue,
|
||||||
|
val memberships: List<Membership>
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
|
||||||
|
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||||
|
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||||
|
var memberships: List<Membership> = Membership.all()
|
||||||
|
|
||||||
|
fun build() = RoomSummaryQueryParams(
|
||||||
|
displayName = displayName,
|
||||||
|
canonicalAlias = canonicalAlias,
|
||||||
|
memberships = memberships
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -41,11 +41,18 @@ interface MembershipService {
|
|||||||
fun getRoomMember(userId: String): RoomMember?
|
fun getRoomMember(userId: String): RoomMember?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the roomMembers ids of the room
|
* Return all the roomMembers of the room with params
|
||||||
*
|
* @param queryParams the params to query for
|
||||||
|
* @return a roomMember list.
|
||||||
|
*/
|
||||||
|
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the roomMembers of the room filtered by memberships
|
||||||
|
* @param queryParams the params to query for
|
||||||
* @return a [LiveData] of roomMember list.
|
* @return a [LiveData] of roomMember list.
|
||||||
*/
|
*/
|
||||||
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers(): Int
|
fun getNumberOfJoinedMembers(): Int
|
||||||
|
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.members
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
|
||||||
|
fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {}): RoomMemberQueryParams {
|
||||||
|
return RoomMemberQueryParams.Builder().apply(init).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to filter room members
|
||||||
|
*/
|
||||||
|
data class RoomMemberQueryParams(
|
||||||
|
val displayName: QueryStringValue,
|
||||||
|
val memberships: List<Membership>
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
|
||||||
|
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||||
|
var memberships: List<Membership> = Membership.all()
|
||||||
|
|
||||||
|
fun build() = RoomMemberQueryParams(
|
||||||
|
displayName = displayName,
|
||||||
|
memberships = memberships
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -43,4 +43,14 @@ enum class Membership(val value: String) {
|
|||||||
fun isLeft(): Boolean {
|
fun isLeft(): Boolean {
|
||||||
return this == KNOCK || this == LEAVE || this == BAN
|
return this == KNOCK || this == LEAVE || this == BAN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun activeMemberships(): List<Membership> {
|
||||||
|
return listOf(INVITE, JOIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun all(): List<Membership> {
|
||||||
|
return values().asList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import com.squareup.moshi.Json
|
|||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the EventType.STATE_CANONICAL_ALIAS state event content
|
* Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class RoomCanonicalAliasContent(
|
data class RoomCanonicalAliasContent(
|
||||||
|
@ -16,23 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the EventType.STATE_ROOM_MEMBER state event content
|
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class RoomMember(
|
data class RoomMember(
|
||||||
@Json(name = "membership") val membership: Membership,
|
val membership: Membership,
|
||||||
@Json(name = "reason") val reason: String? = null,
|
val userId: String,
|
||||||
@Json(name = "displayname") val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
@Json(name = "avatar_url") val avatarUrl: String? = null,
|
val avatarUrl: String? = null
|
||||||
@Json(name = "is_direct") val isDirect: Boolean = false,
|
)
|
||||||
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
|
|
||||||
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
|
|
||||||
) {
|
|
||||||
val safeReason
|
|
||||||
get() = reason?.takeIf { it.isNotBlank() }
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing the EventType.STATE_ROOM_MEMBER state event content
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RoomMemberContent(
|
||||||
|
@Json(name = "membership") val membership: Membership,
|
||||||
|
@Json(name = "reason") val reason: String? = null,
|
||||||
|
@Json(name = "displayname") val displayName: String? = null,
|
||||||
|
@Json(name = "avatar_url") val avatarUrl: String? = null,
|
||||||
|
@Json(name = "is_direct") val isDirect: Boolean = false,
|
||||||
|
@Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null,
|
||||||
|
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
|
||||||
|
) {
|
||||||
|
val safeReason
|
||||||
|
get() = reason?.takeIf { it.isNotBlank() }
|
||||||
|
}
|
@ -43,7 +43,8 @@ data class RoomSummary(
|
|||||||
val membership: Membership = Membership.NONE,
|
val membership: Membership = Membership.NONE,
|
||||||
val versioningState: VersioningState = VersioningState.NONE,
|
val versioningState: VersioningState = VersioningState.NONE,
|
||||||
val readMarkerId: String? = null,
|
val readMarkerId: String? = null,
|
||||||
val userDrafts: List<UserDraft> = emptyList()
|
val userDrafts: List<UserDraft> = emptyList(),
|
||||||
|
var isEncrypted: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isVersioned: Boolean
|
val isVersioned: Boolean
|
||||||
|
@ -145,13 +145,13 @@ class CreateRoomParams {
|
|||||||
*/
|
*/
|
||||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
||||||
// Remove the existing value if any.
|
// Remove the existing value if any.
|
||||||
initialStates?.removeAll { it.getClearType() == EventType.STATE_HISTORY_VISIBILITY }
|
initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY }
|
||||||
|
|
||||||
if (historyVisibility != null) {
|
if (historyVisibility != null) {
|
||||||
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
||||||
contentMap["history_visibility"] = historyVisibility
|
contentMap["history_visibility"] = historyVisibility
|
||||||
|
|
||||||
val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
|
val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
stateKey = "",
|
stateKey = "",
|
||||||
content = contentMap.toContent())
|
content = contentMap.toContent())
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ interface RelationService {
|
|||||||
/**
|
/**
|
||||||
* Reply to an event in the timeline (must be in same room)
|
* Reply to an event in the timeline (must be in same room)
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
|
||||||
* The replyText can be a Spannable and contains special spans (UserMentionSpan) that will be translated
|
* The replyText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||||
* by the sdk into pills.
|
* by the sdk into pills.
|
||||||
* @param eventReplied the event referenced by the reply
|
* @param eventReplied the event referenced by the reply
|
||||||
* @param replyText the reply text
|
* @param replyText the reply text
|
||||||
@ -108,5 +108,17 @@ interface RelationService {
|
|||||||
replyText: CharSequence,
|
replyText: CharSequence,
|
||||||
autoMarkdown: Boolean = false): Cancelable?
|
autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
|
/**
|
||||||
|
* Get the current EventAnnotationsSummary
|
||||||
|
* @param eventId the eventId to look for EventAnnotationsSummary
|
||||||
|
* @return the EventAnnotationsSummary found
|
||||||
|
*/
|
||||||
|
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a LiveData of EventAnnotationsSummary for the specified eventId
|
||||||
|
* @param eventId the eventId to look for EventAnnotationsSummary
|
||||||
|
* @return the LiveData of EventAnnotationsSummary
|
||||||
|
*/
|
||||||
|
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@ package im.vector.matrix.android.api.session.room.send
|
|||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag class for spans that should mention a user.
|
* Tag class for spans that should mention a matrix item.
|
||||||
* These Spans will be transformed into pills when detected in message to send
|
* These Spans will be transformed into pills when detected in message to send
|
||||||
*/
|
*/
|
||||||
interface UserMentionSpan {
|
interface MatrixItemSpan {
|
||||||
val matrixItem: MatrixItem
|
val matrixItem: MatrixItem
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ interface SendService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a text message asynchronously.
|
* Method to send a text message asynchronously.
|
||||||
* The text to send can be a Spannable and contains special spans (UserMentionSpan) that will be translated
|
* The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated
|
||||||
* by the sdk into pills.
|
* by the sdk into pills.
|
||||||
* @param text the text message to send
|
* @param text the text message to send
|
||||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
||||||
|
@ -65,7 +65,7 @@ interface Timeline {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main method to enrich the timeline with new data.
|
* This is the main method to enrich the timeline with new data.
|
||||||
* It will call the onUpdated method from [Listener] when the data will be processed.
|
* It will call the onTimelineUpdated method from [Listener] when the data will be processed.
|
||||||
* It also ensures only one pagination by direction is launched at a time, so you can safely call this multiple time in a row.
|
* It also ensures only one pagination by direction is launched at a time, so you can safely call this multiple time in a row.
|
||||||
*/
|
*/
|
||||||
fun paginate(direction: Direction, count: Int)
|
fun paginate(direction: Direction, count: Int)
|
||||||
@ -106,7 +106,12 @@ interface Timeline {
|
|||||||
* Call when the timeline has been updated through pagination or sync.
|
* Call when the timeline has been updated through pagination or sync.
|
||||||
* @param snapshot the most up to date snapshot
|
* @param snapshot the most up to date snapshot
|
||||||
*/
|
*/
|
||||||
fun onUpdated(snapshot: List<TimelineEvent>)
|
fun onTimelineUpdated(snapshot: List<TimelineEvent>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever an error we can't recover from occurred
|
||||||
|
*/
|
||||||
|
fun onTimelineFailure(throwable: Throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,25 +50,25 @@ interface UserService {
|
|||||||
* @param userId the userId to look for.
|
* @param userId the userId to look for.
|
||||||
* @return a LiveData of user with userId
|
* @return a LiveData of user with userId
|
||||||
*/
|
*/
|
||||||
fun liveUser(userId: String): LiveData<Optional<User>>
|
fun getUserLive(userId: String): LiveData<Optional<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live list of users sorted alphabetically
|
* Observe a live list of users sorted alphabetically
|
||||||
* @return a Livedata of users
|
* @return a Livedata of users
|
||||||
*/
|
*/
|
||||||
fun liveUsers(): LiveData<List<User>>
|
fun getUsersLive(): LiveData<List<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
* @param filter the filter. It will look into userId and displayName.
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
* @return a Livedata of users
|
* @return a Livedata of users
|
||||||
*/
|
*/
|
||||||
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
|
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of ignored users
|
* Get list of ignored users
|
||||||
*/
|
*/
|
||||||
fun liveIgnoredUsers(): LiveData<List<User>>
|
fun getIgnoredUsersLive(): LiveData<List<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignore users
|
* Ignore users
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
|
|||||||
|
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
@ -62,6 +63,9 @@ sealed class MatrixItem(
|
|||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
|
override fun getBestName() = id
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GroupItem(override val id: String,
|
data class GroupItem(override val id: String,
|
||||||
@ -71,9 +75,12 @@ sealed class MatrixItem(
|
|||||||
init {
|
init {
|
||||||
if (BuildConfig.DEBUG) checkId()
|
if (BuildConfig.DEBUG) checkId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
|
override fun getBestName() = id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBestName(): String {
|
open fun getBestName(): String {
|
||||||
return displayName?.takeIf { it.isNotBlank() } ?: id
|
return displayName?.takeIf { it.isNotBlank() } ?: id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +102,7 @@ sealed class MatrixItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun firstLetterOfDisplayName(): String {
|
fun firstLetterOfDisplayName(): String {
|
||||||
return getBestName()
|
return (displayName?.takeIf { it.isNotBlank() } ?: id)
|
||||||
.let { dn ->
|
.let { dn ->
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
val initial = dn[startIndex]
|
val initial = dn[startIndex]
|
||||||
@ -138,4 +145,6 @@ sealed class MatrixItem(
|
|||||||
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
||||||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||||
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
||||||
|
fun RoomMember.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.util.md5
|
||||||
|
|
||||||
|
internal fun createSessionId(userId: String, deviceId: String?): String {
|
||||||
|
return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
|
||||||
|
}
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.db
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.auth.createSessionId
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import io.realm.DynamicRealm
|
import io.realm.DynamicRealm
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -23,35 +26,60 @@ import timber.log.Timber
|
|||||||
internal object AuthRealmMigration : RealmMigration {
|
internal object AuthRealmMigration : RealmMigration {
|
||||||
|
|
||||||
// Current schema version
|
// Current schema version
|
||||||
const val SCHEMA_VERSION = 2L
|
const val SCHEMA_VERSION = 3L
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||||
|
|
||||||
if (oldVersion <= 0) {
|
if (oldVersion <= 0) migrateTo1(realm)
|
||||||
Timber.d("Step 0 -> 1")
|
if (oldVersion <= 1) migrateTo2(realm)
|
||||||
Timber.d("Create PendingSessionEntity")
|
if (oldVersion <= 2) migrateTo3(realm)
|
||||||
|
}
|
||||||
|
|
||||||
realm.schema.create("PendingSessionEntity")
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
Timber.d("Step 0 -> 1")
|
||||||
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
|
Timber.d("Create PendingSessionEntity")
|
||||||
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
|
|
||||||
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
|
|
||||||
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
|
|
||||||
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
|
|
||||||
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
|
|
||||||
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
|
|
||||||
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
|
||||||
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion <= 1) {
|
realm.schema.create("PendingSessionEntity")
|
||||||
Timber.d("Step 1 -> 2")
|
.addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
|
||||||
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
.setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
|
||||||
|
.addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
|
||||||
|
.setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
|
||||||
|
.addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
|
||||||
|
.setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
|
||||||
|
.addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
|
||||||
|
.addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
|
||||||
|
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
||||||
|
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
realm.schema.get("SessionParamsEntity")
|
private fun migrateTo2(realm: DynamicRealm) {
|
||||||
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
Timber.d("Step 1 -> 2")
|
||||||
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
||||||
}
|
|
||||||
|
realm.schema.get("SessionParamsEntity")
|
||||||
|
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
||||||
|
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateTo3(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 2 -> 3")
|
||||||
|
Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
|
||||||
|
|
||||||
|
realm.schema.get("SessionParamsEntity")
|
||||||
|
?.removePrimaryKey()
|
||||||
|
?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
|
||||||
|
?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
|
||||||
|
?.transform {
|
||||||
|
val userId = it.getString(SessionParamsEntityFields.USER_ID)
|
||||||
|
val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
|
||||||
|
|
||||||
|
val credentials = MoshiProvider.providesMoshi()
|
||||||
|
.adapter(Credentials::class.java)
|
||||||
|
.fromJson(credentialsJson)
|
||||||
|
|
||||||
|
it.set(SessionParamsEntityFields.SESSION_ID, createSessionId(userId, credentials?.deviceId))
|
||||||
|
}
|
||||||
|
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ import io.realm.RealmObject
|
|||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class SessionParamsEntity(
|
internal open class SessionParamsEntity(
|
||||||
@PrimaryKey var userId: String = "",
|
@PrimaryKey var sessionId: String = "",
|
||||||
|
var userId: String = "",
|
||||||
var credentialsJson: String = "",
|
var credentialsJson: String = "",
|
||||||
var homeServerConnectionConfigJson: String = "",
|
var homeServerConnectionConfigJson: String = "",
|
||||||
// Set to false when the token is invalid and the user has been soft logged out
|
// Set to false when the token is invalid and the user has been soft logged out
|
||||||
|
@ -20,6 +20,7 @@ import com.squareup.moshi.Moshi
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.internal.auth.createSessionId
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
@ -49,6 +50,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return SessionParamsEntity(
|
return SessionParamsEntity(
|
||||||
|
createSessionId(sessionParams.credentials.userId, sessionParams.credentials.deviceId),
|
||||||
sessionParams.credentials.userId,
|
sessionParams.credentials.userId,
|
||||||
credentialsJson,
|
credentialsJson,
|
||||||
homeServerConnectionConfigJson,
|
homeServerConnectionConfigJson,
|
||||||
|
@ -47,7 +47,7 @@ internal abstract class CryptoModule {
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
internal const val DB_ALIAS_PREFIX = "crypto_module_"
|
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@ -59,7 +59,7 @@ internal abstract class CryptoModule {
|
|||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, "$DB_ALIAS_PREFIX$userMd5")
|
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.name("crypto_store.realm")
|
.name("crypto_store.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
@ -123,6 +123,9 @@ internal abstract class CryptoModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
abstract fun bindGetDevicesTask(getDevicesTask: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
abstract fun bindSetDeviceNameTask(setDeviceNameTask: DefaultSetDeviceNameTask): SetDeviceNameTask
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
@ -127,6 +128,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val getDevicesTask: GetDevicesTask,
|
private val getDevicesTask: GetDevicesTask,
|
||||||
|
private val getDeviceInfoTask: GetDeviceInfoTask,
|
||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
@ -145,17 +147,17 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
fun onStateEvent(roomId: String, event: Event) {
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLiveEvent(roomId: String, event: Event) {
|
fun onLiveEvent(roomId: String, event: Event) {
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +201,14 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||||
|
getDeviceInfoTask
|
||||||
|
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,18 @@ internal interface CryptoApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the devices list
|
* Get the devices list
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-devices
|
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
|
||||||
fun getDevices(): Call<DevicesListResponse>
|
fun getDevices(): Call<DevicesListResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device info by id
|
||||||
|
* Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}")
|
||||||
|
fun getDeviceInfo(@Path("deviceId") deviceId: String): Call<DeviceInfo>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload device and/or one-time keys.
|
* Upload device and/or one-time keys.
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-upload
|
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-upload
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.tasks
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetDeviceInfoTask : Task<GetDeviceInfoTask.Params, DeviceInfo> {
|
||||||
|
data class Params(val deviceId: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetDeviceInfoTask @Inject constructor(private val cryptoApi: CryptoApi)
|
||||||
|
: GetDeviceInfoTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = cryptoApi.getDeviceInfo(params.deviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,12 +19,16 @@ package im.vector.matrix.android.internal.database
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import io.realm.*
|
import io.realm.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
internal interface LiveEntityObserver {
|
internal interface LiveEntityObserver {
|
||||||
fun start()
|
fun start()
|
||||||
fun dispose()
|
fun dispose()
|
||||||
|
fun cancelProcess()
|
||||||
fun isStarted(): Boolean
|
fun isStarted(): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +39,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
|||||||
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
|
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected val observerScope = CoroutineScope(SupervisorJob())
|
||||||
protected abstract val query: Monarchy.Query<T>
|
protected abstract val query: Monarchy.Query<T>
|
||||||
private val isStarted = AtomicBoolean(false)
|
private val isStarted = AtomicBoolean(false)
|
||||||
private val backgroundRealm = AtomicReference<Realm>()
|
private val backgroundRealm = AtomicReference<Realm>()
|
||||||
@ -59,10 +64,15 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
|||||||
backgroundRealm.getAndSet(null).also {
|
backgroundRealm.getAndSet(null).also {
|
||||||
it.close()
|
it.close()
|
||||||
}
|
}
|
||||||
|
observerScope.coroutineContext.cancelChildren()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun cancelProcess() {
|
||||||
|
observerScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
|
||||||
override fun isStarted(): Boolean {
|
override fun isStarted(): Boolean {
|
||||||
return isStarted.get()
|
return isStarted.get()
|
||||||
}
|
}
|
||||||
|
@ -16,43 +16,36 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
|
||||||
import io.realm.*
|
import io.realm.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import kotlinx.coroutines.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
class RealmQueryLatch<E : RealmObject>(private val realmConfiguration: RealmConfiguration,
|
internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration,
|
||||||
private val realmQueryBuilder: (Realm) -> RealmQuery<E>) {
|
timeoutMillis: Long,
|
||||||
|
builder: (Realm) -> RealmQuery<T>) {
|
||||||
|
withTimeout(timeoutMillis) {
|
||||||
|
// Confine Realm interaction to a single thread with Looper.
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val latch = CompletableDeferred<Unit>()
|
||||||
|
|
||||||
private companion object {
|
Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val QUERY_LATCH_HANDLER = createBackgroundHandler("REALM_QUERY_LATCH")
|
val result = builder(realm).findAllAsync()
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(InterruptedException::class)
|
val listener = object : RealmChangeListener<RealmResults<T>> {
|
||||||
fun await(timeout: Long, timeUnit: TimeUnit) {
|
override fun onChange(it: RealmResults<T>) {
|
||||||
val realmRef = AtomicReference<Realm>()
|
if (it.isNotEmpty()) {
|
||||||
val latch = CountDownLatch(1)
|
result.removeChangeListener(this)
|
||||||
QUERY_LATCH_HANDLER.post {
|
latch.complete(Unit)
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
}
|
||||||
realmRef.set(realm)
|
|
||||||
val result = realmQueryBuilder(realm).findAllAsync()
|
|
||||||
result.addChangeListener(object : RealmChangeListener<RealmResults<E>> {
|
|
||||||
override fun onChange(t: RealmResults<E>) {
|
|
||||||
if (t.isNotEmpty()) {
|
|
||||||
result.removeChangeListener(this)
|
|
||||||
latch.countDown()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
result.addChangeListener(listener)
|
||||||
try {
|
try {
|
||||||
latch.await(timeout, timeUnit)
|
latch.await()
|
||||||
} catch (exception: InterruptedException) {
|
} catch (e: CancellationException) {
|
||||||
throw exception
|
result.removeChangeListener(listener)
|
||||||
} finally {
|
throw e
|
||||||
QUERY_LATCH_HANDLER.post {
|
}
|
||||||
realmRef.getAndSet(null).close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
import im.vector.matrix.android.internal.di.UserCacheDirectory
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.session.SessionModule
|
import im.vector.matrix.android.internal.session.SessionModule
|
||||||
@ -37,13 +38,14 @@ private const val REALM_NAME = "disk_store.realm"
|
|||||||
*/
|
*/
|
||||||
internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils,
|
internal class SessionRealmConfigurationFactory @Inject constructor(private val realmKeysUtils: RealmKeysUtils,
|
||||||
@UserCacheDirectory val directory: File,
|
@UserCacheDirectory val directory: File,
|
||||||
|
@SessionId val sessionId: String,
|
||||||
@UserMd5 val userMd5: String,
|
@UserMd5 val userMd5: String,
|
||||||
context: Context) {
|
context: Context) {
|
||||||
|
|
||||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun create(): RealmConfiguration {
|
fun create(): RealmConfiguration {
|
||||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false)
|
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||||
if (shouldClearRealm) {
|
if (shouldClearRealm) {
|
||||||
Timber.v("************************************************************")
|
Timber.v("************************************************************")
|
||||||
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
||||||
@ -53,14 +55,15 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
|||||||
}
|
}
|
||||||
sharedPreferences
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", true)
|
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
val realmConfiguration = RealmConfiguration.Builder()
|
||||||
|
.compactOnLaunch()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.name(REALM_NAME)
|
.name(REALM_NAME)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, "${SessionModule.DB_ALIAS_PREFIX}$userMd5")
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.modules(SessionRealmModule())
|
.modules(SessionRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
@ -71,7 +74,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
|||||||
Timber.v("Successfully create realm instance")
|
Timber.v("Successfully create realm instance")
|
||||||
sharedPreferences
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$userMd5", false)
|
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
return realmConfiguration
|
return realmConfiguration
|
||||||
|
@ -21,27 +21,14 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
// By default if a chunk is empty we consider it unlinked
|
|
||||||
internal fun ChunkEntity.isUnlinked(): Boolean {
|
|
||||||
assertIsManaged()
|
|
||||||
return timelineEvents.where()
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
|
||||||
.findAll()
|
|
||||||
.isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
@ -51,11 +38,10 @@ internal fun ChunkEntity.deleteOnCascade() {
|
|||||||
|
|
||||||
internal fun ChunkEntity.merge(roomId: String,
|
internal fun ChunkEntity.merge(roomId: String,
|
||||||
chunkToMerge: ChunkEntity,
|
chunkToMerge: ChunkEntity,
|
||||||
direction: PaginationDirection) {
|
direction: PaginationDirection): List<TimelineEventEntity> {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked()
|
val isChunkToMergeUnlinked = chunkToMerge.isUnlinked
|
||||||
val isCurrentChunkUnlinked = this.isUnlinked()
|
val isCurrentChunkUnlinked = isUnlinked
|
||||||
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
|
||||||
|
|
||||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||||
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||||
@ -70,49 +56,21 @@ internal fun ChunkEntity.merge(roomId: String,
|
|||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
this.isLastBackward = chunkToMerge.isLastBackward
|
||||||
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
}
|
}
|
||||||
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
|
return eventsToMerge
|
||||||
val eventIds = ArrayList<String>()
|
.mapNotNull {
|
||||||
events.forEach { event ->
|
val event = it.root?.asDomain() ?: return@mapNotNull null
|
||||||
add(roomId, event, direction, isUnlinked = isUnlinked)
|
add(roomId, event, direction)
|
||||||
if (event.eventId != null) {
|
}
|
||||||
eventIds.add(event.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateSenderDataFor(eventIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ChunkEntity.addAll(roomId: String,
|
|
||||||
events: List<Event>,
|
|
||||||
direction: PaginationDirection,
|
|
||||||
stateIndexOffset: Int = 0,
|
|
||||||
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
|
|
||||||
isUnlinked: Boolean = false) {
|
|
||||||
assertIsManaged()
|
|
||||||
val eventIds = ArrayList<String>()
|
|
||||||
events.forEach { event ->
|
|
||||||
add(roomId, event, direction, stateIndexOffset, isUnlinked)
|
|
||||||
if (event.eventId != null) {
|
|
||||||
eventIds.add(event.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateSenderDataFor(eventIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
|
|
||||||
for (eventId in eventIds) {
|
|
||||||
val timelineEventEntity = timelineEvents.find(eventId) ?: continue
|
|
||||||
timelineEventEntity.updateSenderData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.add(roomId: String,
|
internal fun ChunkEntity.add(roomId: String,
|
||||||
event: Event,
|
event: Event,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
stateIndexOffset: Int = 0,
|
stateIndexOffset: Int = 0
|
||||||
isUnlinked: Boolean = false) {
|
): TimelineEventEntity? {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
@ -134,12 +92,15 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isChunkUnlinked = isUnlinked
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
val eventId = event.eventId ?: ""
|
val eventId = event.eventId ?: ""
|
||||||
val senderId = event.senderId ?: ""
|
val senderId = event.senderId ?: ""
|
||||||
|
|
||||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
||||||
?: ReadReceiptsSummaryEntity(eventId, roomId)
|
?: realm.createObject<ReadReceiptsSummaryEntity>(eventId).apply {
|
||||||
|
this.roomId = roomId
|
||||||
|
}
|
||||||
|
|
||||||
// Update RR for the sender of a new message with a dummy one
|
// Update RR for the sender of a new message with a dummy one
|
||||||
|
|
||||||
@ -156,13 +117,15 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val eventEntity = TimelineEventEntity(localId).also {
|
val rootEvent = event.toEntity(roomId).apply {
|
||||||
it.root = event.toEntity(roomId).apply {
|
this.stateIndex = currentStateIndex
|
||||||
this.stateIndex = currentStateIndex
|
this.displayIndex = currentDisplayIndex
|
||||||
this.isUnlinked = isUnlinked
|
this.sendState = SendState.SYNCED
|
||||||
this.displayIndex = currentDisplayIndex
|
this.isUnlinked = isChunkUnlinked
|
||||||
this.sendState = SendState.SYNCED
|
}
|
||||||
}
|
val eventEntity = realm.createObject<TimelineEventEntity>().also {
|
||||||
|
it.localId = localId
|
||||||
|
it.root = realm.copyToRealm(rootEvent)
|
||||||
it.eventId = eventId
|
it.eventId = eventId
|
||||||
it.roomId = roomId
|
it.roomId = roomId
|
||||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||||
@ -170,6 +133,7 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
}
|
}
|
||||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||||
timelineEvents.add(position, eventEntity)
|
timelineEvents.add(position, eventEntity)
|
||||||
|
return eventEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||||
|
@ -60,7 +60,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
|||||||
this.sendState = SendState.UNSENT
|
this.sendState = SendState.UNSENT
|
||||||
}
|
}
|
||||||
val roomMembers = RoomMembers(realm, roomId)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
val myUser = roomMembers.get(senderId)
|
val myUser = roomMembers.getLastRoomMember(senderId)
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||||
it.root = eventEntity
|
it.root = eventEntity
|
||||||
@ -69,7 +69,6 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
|
|||||||
it.senderName = myUser?.displayName
|
it.senderName = myUser?.displayName
|
||||||
it.senderAvatar = myUser?.avatarUrl
|
it.senderAvatar = myUser?.avatarUrl
|
||||||
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
||||||
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
|
|
||||||
}
|
}
|
||||||
sendingTimelineEvents.add(0, timelineEventEntity)
|
sendingTimelineEvents.add(0, timelineEventEntity)
|
||||||
}
|
}
|
||||||
|
@ -16,74 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.helper
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
|
||||||
import im.vector.matrix.android.internal.database.model.*
|
|
||||||
import im.vector.matrix.android.internal.database.query.next
|
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmList
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
|
|
||||||
internal fun TimelineEventEntity.updateSenderData() {
|
|
||||||
assertIsManaged()
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
|
|
||||||
val stateIndex = root?.stateIndex ?: return
|
|
||||||
val senderId = root?.sender ?: return
|
|
||||||
val chunkEntity = chunk?.firstOrNull() ?: return
|
|
||||||
val isUnlinked = chunkEntity.isUnlinked()
|
|
||||||
var senderMembershipEvent: EventEntity?
|
|
||||||
var senderRoomMemberContent: String?
|
|
||||||
var senderRoomMemberPrevContent: String?
|
|
||||||
when {
|
|
||||||
stateIndex <= 0 -> {
|
|
||||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
|
||||||
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
|
||||||
senderRoomMemberContent = senderMembershipEvent?.content
|
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
|
||||||
if (senderMembershipEvent == null) {
|
|
||||||
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
|
||||||
.where()
|
|
||||||
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
|
||||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
|
||||||
.prev(since = stateIndex)
|
|
||||||
senderRoomMemberContent = senderMembershipEvent?.content
|
|
||||||
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentMapper.map(senderRoomMemberContent).toModel<RoomMember>()?.also {
|
|
||||||
this.senderAvatar = it.avatarUrl
|
|
||||||
this.senderName = it.displayName
|
|
||||||
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We try to fallback on prev content if we got a room member state events with null fields
|
|
||||||
if (root?.type == EventType.STATE_ROOM_MEMBER) {
|
|
||||||
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMember>()?.also {
|
|
||||||
if (this.senderAvatar == null && it.avatarUrl != null) {
|
|
||||||
this.senderAvatar = it.avatarUrl
|
|
||||||
}
|
|
||||||
if (this.senderName == null && it.displayName != null) {
|
|
||||||
this.senderName = it.displayName
|
|
||||||
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.senderMembershipEvent = senderMembershipEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
||||||
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
|
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
|
||||||
@ -93,10 +28,3 @@ internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
|
|||||||
currentIdNum.toLong() + 1
|
currentIdNum.toLong() + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
|
||||||
return where()
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
|
||||||
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
|
import im.vector.matrix.android.internal.database.query.next
|
||||||
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an internal cache to avoid querying all the time the room member events
|
||||||
|
*/
|
||||||
|
@SessionScope
|
||||||
|
internal class TimelineEventSenderVisitor @Inject constructor() {
|
||||||
|
|
||||||
|
internal data class Key(
|
||||||
|
val roomId: String,
|
||||||
|
val stateIndex: Int,
|
||||||
|
val senderId: String
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class Value(
|
||||||
|
var senderAvatar: String? = null,
|
||||||
|
var senderName: String? = null,
|
||||||
|
var isUniqueDisplayName: Boolean = false,
|
||||||
|
var senderMembershipEventId: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private val values = HashMap<Key, Value>()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
values.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear(roomId: String, senderId: String) {
|
||||||
|
val keysToRemove = values.keys.filter { it.senderId == senderId && it.roomId == roomId }
|
||||||
|
keysToRemove.forEach {
|
||||||
|
values.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(timelineEventEntities: List<TimelineEventEntity>) = timelineEventEntities.forEach { visit(it) }
|
||||||
|
|
||||||
|
fun visit(timelineEventEntity: TimelineEventEntity) {
|
||||||
|
if (!timelineEventEntity.isValid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val key = Key(
|
||||||
|
roomId = timelineEventEntity.roomId,
|
||||||
|
stateIndex = timelineEventEntity.root?.stateIndex ?: 0,
|
||||||
|
senderId = timelineEventEntity.root?.sender ?: ""
|
||||||
|
)
|
||||||
|
val result = values.getOrPut(key) {
|
||||||
|
timelineEventEntity.computeValue()
|
||||||
|
}
|
||||||
|
timelineEventEntity.apply {
|
||||||
|
this.isUniqueDisplayName = result.isUniqueDisplayName
|
||||||
|
this.senderAvatar = result.senderAvatar
|
||||||
|
this.senderName = result.senderName
|
||||||
|
this.senderMembershipEventId = result.senderMembershipEventId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
||||||
|
return where()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TimelineEventEntity.computeValue(): Value {
|
||||||
|
assertIsManaged()
|
||||||
|
val result = Value()
|
||||||
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return result
|
||||||
|
val stateIndex = root?.stateIndex ?: return result
|
||||||
|
val senderId = root?.sender ?: return result
|
||||||
|
val chunkEntity = chunk?.firstOrNull() ?: return result
|
||||||
|
val isUnlinked = chunkEntity.isUnlinked
|
||||||
|
var senderMembershipEvent: EventEntity?
|
||||||
|
var senderRoomMemberContent: String?
|
||||||
|
var senderRoomMemberPrevContent: String?
|
||||||
|
|
||||||
|
if (stateIndex <= 0) {
|
||||||
|
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
||||||
|
senderRoomMemberPrevContent = senderMembershipEvent?.content
|
||||||
|
} else {
|
||||||
|
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
|
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
||||||
|
if (senderMembershipEvent == null) {
|
||||||
|
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
||||||
|
.where()
|
||||||
|
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
||||||
|
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
|
.prev(since = stateIndex)
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
|
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
|
||||||
|
result.senderAvatar = it.avatarUrl
|
||||||
|
result.senderName = it.displayName
|
||||||
|
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||||
|
}
|
||||||
|
// We try to fallback on prev content if we got a room member state events with null fields
|
||||||
|
if (root?.type == EventType.STATE_ROOM_MEMBER) {
|
||||||
|
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMemberContent>()?.also {
|
||||||
|
if (result.senderAvatar == null && it.avatarUrl != null) {
|
||||||
|
result.senderAvatar = it.avatarUrl
|
||||||
|
}
|
||||||
|
if (result.senderName == null && it.displayName != null) {
|
||||||
|
result.senderName = it.displayName
|
||||||
|
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.senderMembershipEventId = senderMembershipEvent?.eventId
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
|
|
||||||
|
internal object RoomMemberMapper {
|
||||||
|
|
||||||
|
fun map(roomMemberEntity: RoomMemberEntity): RoomMember {
|
||||||
|
return RoomMember(
|
||||||
|
userId = roomMemberEntity.userId,
|
||||||
|
avatarUrl = roomMemberEntity.avatarUrl,
|
||||||
|
displayName = roomMemberEntity.displayName,
|
||||||
|
membership = roomMemberEntity.membership
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun RoomMemberEntity.asDomain(): RoomMember {
|
||||||
|
return RoomMemberMapper.map(this)
|
||||||
|
}
|
@ -72,7 +72,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||||||
readMarkerId = roomSummaryEntity.readMarkerId,
|
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||||
aliases = roomSummaryEntity.aliases.toList()
|
aliases = roomSummaryEntity.aliases.toList(),
|
||||||
|
isEncrypted = roomSummaryEntity.isEncrypted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
|
|||||||
var backwardsDisplayIndex: Int? = null,
|
var backwardsDisplayIndex: Int? = null,
|
||||||
var forwardsDisplayIndex: Int? = null,
|
var forwardsDisplayIndex: Int? = null,
|
||||||
var backwardsStateIndex: Int? = null,
|
var backwardsStateIndex: Int? = null,
|
||||||
var forwardsStateIndex: Int? = null
|
var forwardsStateIndex: Int? = null,
|
||||||
|
var isUnlinked: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
fun identifier() = "${prevToken}_$nextToken"
|
fun identifier() = "${prevToken}_$nextToken"
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
|
||||||
|
@Index var userId: String = "",
|
||||||
|
@Index var roomId: String = "",
|
||||||
|
var displayName: String = "",
|
||||||
|
var avatarUrl: String = "",
|
||||||
|
var reason: String? = null,
|
||||||
|
var isDirect: Boolean = false
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
var membership: Membership
|
||||||
|
get() {
|
||||||
|
return Membership.valueOf(membershipStr)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
membershipStr = value.name
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
@ -42,7 +42,9 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||||||
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||||
var canonicalAlias: String? = null,
|
var canonicalAlias: String? = null,
|
||||||
var aliases: RealmList<String> = RealmList(),
|
var aliases: RealmList<String> = RealmList(),
|
||||||
var flatAliases: String = ""
|
// this is required for querying
|
||||||
|
var flatAliases: String = "",
|
||||||
|
var isEncrypted: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
@ -49,6 +49,7 @@ import io.realm.annotations.RealmModule
|
|||||||
ReadMarkerEntity::class,
|
ReadMarkerEntity::class,
|
||||||
UserDraftsEntity::class,
|
UserDraftsEntity::class,
|
||||||
DraftEntity::class,
|
DraftEntity::class,
|
||||||
HomeServerCapabilitiesEntity::class
|
HomeServerCapabilitiesEntity::class,
|
||||||
|
RoomMemberEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
@ -29,7 +29,7 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
|||||||
var senderName: String? = null,
|
var senderName: String? = null,
|
||||||
var isUniqueDisplayName: Boolean = false,
|
var isUniqueDisplayName: Boolean = false,
|
||||||
var senderAvatar: String? = null,
|
var senderAvatar: String? = null,
|
||||||
var senderMembershipEvent: EventEntity? = null,
|
var senderMembershipEventId: String? = null,
|
||||||
var readReceipts: ReadReceiptsSummaryEntity? = null
|
var readReceipts: ReadReceiptsSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
@ -57,9 +57,15 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str
|
|||||||
return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull()
|
return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.Companion.create(realm: Realm, prevToken: String?, nextToken: String?): ChunkEntity {
|
internal fun ChunkEntity.Companion.create(
|
||||||
|
realm: Realm,
|
||||||
|
prevToken: String?,
|
||||||
|
nextToken: String?,
|
||||||
|
isUnlinked: Boolean
|
||||||
|
): ChunkEntity {
|
||||||
return realm.createObject<ChunkEntity>().apply {
|
return realm.createObject<ChunkEntity>().apply {
|
||||||
this.prevToken = prevToken
|
this.prevToken = prevToken
|
||||||
this.nextToken = nextToken
|
this.nextToken = nextToken
|
||||||
|
this.isUnlinked = isUnlinked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun RoomMemberEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberEntity> {
|
||||||
|
val query = realm
|
||||||
|
.where<RoomMemberEntity>()
|
||||||
|
.equalTo(RoomMemberEntityFields.ROOM_ID, roomId)
|
||||||
|
|
||||||
|
if (userId != null) {
|
||||||
|
query.equalTo(RoomMemberEntityFields.USER_ID, userId)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
@ -54,7 +54,7 @@ internal fun TimelineEventEntity.Companion.where(realm: Realm,
|
|||||||
|
|
||||||
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
||||||
return realm.where<TimelineEventEntity>()
|
return realm.where<TimelineEventEntity>()
|
||||||
.equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT.EVENT_ID, senderMembershipEventId)
|
.equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId)
|
||||||
.findAll()
|
.findAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
internal object MatrixModule {
|
internal object MatrixModule {
|
||||||
@ -38,8 +37,7 @@ internal object MatrixModule {
|
|||||||
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
||||||
computation = Dispatchers.Default,
|
computation = Dispatchers.Default,
|
||||||
main = Dispatchers.Main,
|
main = Dispatchers.Main,
|
||||||
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
|
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher()
|
||||||
sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,3 +31,10 @@ internal annotation class UserId
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
internal annotation class UserMd5
|
internal annotation class UserMd5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to inject the sessionId, which is defined as md5(userId|deviceId)
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class SessionId
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import com.novoda.merlin.Merlin
|
import com.novoda.merlin.Merlin
|
||||||
import com.novoda.merlin.MerlinsBeard
|
import com.novoda.merlin.MerlinsBeard
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
import im.vector.matrix.android.internal.di.MatrixScope
|
||||||
@ -28,8 +29,8 @@ import kotlin.coroutines.resume
|
|||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
internal class NetworkConnectivityChecker @Inject constructor(context: Context,
|
internal class NetworkConnectivityChecker @Inject constructor(private val context: Context,
|
||||||
backgroundDetectionObserver: BackgroundDetectionObserver)
|
private val backgroundDetectionObserver: BackgroundDetectionObserver)
|
||||||
: BackgroundDetectionObserver.Listener {
|
: BackgroundDetectionObserver.Listener {
|
||||||
|
|
||||||
private val merlin = Merlin.Builder()
|
private val merlin = Merlin.Builder()
|
||||||
@ -37,19 +38,33 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context,
|
|||||||
.withDisconnectableCallbacks()
|
.withDisconnectableCallbacks()
|
||||||
.build(context)
|
.build(context)
|
||||||
|
|
||||||
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
|
private val merlinsBeard = MerlinsBeard.Builder().build(context)
|
||||||
|
|
||||||
// True when internet is available
|
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
|
||||||
var hasInternetAccess = MerlinsBeard.Builder().build(context).isConnected
|
private var hasInternetAccess = merlinsBeard.isConnected
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
backgroundDetectionObserver.register(this)
|
backgroundDetectionObserver.register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when internet is available
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
fun hasInternetAccess(): Boolean {
|
||||||
|
// If we are in background we have unbound merlin, so we have to check
|
||||||
|
return if (backgroundDetectionObserver.isInBackground) {
|
||||||
|
merlinsBeard.hasInternetAccess()
|
||||||
|
} else {
|
||||||
|
hasInternetAccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMoveToForeground() {
|
override fun onMoveToForeground() {
|
||||||
merlin.bind()
|
merlin.bind()
|
||||||
|
merlinsBeard.hasInternetAccess {
|
||||||
|
hasInternetAccess = it
|
||||||
|
}
|
||||||
merlin.registerDisconnectable {
|
merlin.registerDisconnectable {
|
||||||
if (hasInternetAccess) {
|
if (hasInternetAccess) {
|
||||||
Timber.v("On Disconnect")
|
Timber.v("On Disconnect")
|
||||||
@ -76,14 +91,17 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context,
|
|||||||
merlin.unbind()
|
merlin.unbind()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In background you won't get notification as merlin is unbound
|
||||||
suspend fun waitUntilConnected() {
|
suspend fun waitUntilConnected() {
|
||||||
if (hasInternetAccess) {
|
if (hasInternetAccess) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
Timber.v("Waiting for network...")
|
||||||
suspendCoroutine<Unit> { continuation ->
|
suspendCoroutine<Unit> { continuation ->
|
||||||
register(object : Listener {
|
register(object : Listener {
|
||||||
override fun onConnect() {
|
override fun onConnect() {
|
||||||
unregister(this)
|
unregister(this)
|
||||||
|
Timber.v("Connected to network...")
|
||||||
continuation.resume(Unit)
|
continuation.resume(Unit)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.query
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
|
||||||
|
fun <T : RealmObject, E : Enum<E>> RealmQuery<T>.process(field: String, enums: List<Enum<E>>): RealmQuery<T> {
|
||||||
|
val lastEnumValue = enums.lastOrNull()
|
||||||
|
beginGroup()
|
||||||
|
for (enumValue in enums) {
|
||||||
|
equalTo(field, enumValue.name)
|
||||||
|
if (enumValue != lastEnumValue) {
|
||||||
|
or()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endGroup()
|
||||||
|
return this
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import io.realm.Case
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||||
|
when (queryStringValue) {
|
||||||
|
is QueryStringValue.NoCondition -> Timber.v("No condition to process")
|
||||||
|
is QueryStringValue.IsNotNull -> isNotNull(field)
|
||||||
|
is QueryStringValue.IsNull -> isNull(field)
|
||||||
|
is QueryStringValue.IsEmpty -> isEmpty(field)
|
||||||
|
is QueryStringValue.IsNotEmpty -> isNotEmpty(field)
|
||||||
|
is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
||||||
|
is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QueryStringValue.Case.toRealmCase(): Case {
|
||||||
|
return when (this) {
|
||||||
|
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
|
||||||
|
QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.file.FileService
|
|||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.md5
|
import im.vector.matrix.android.internal.util.md5
|
||||||
@ -42,7 +42,7 @@ import java.io.IOException
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultFileService @Inject constructor(private val context: Context,
|
internal class DefaultFileService @Inject constructor(private val context: Context,
|
||||||
@UserMd5 private val userMd5: String,
|
@SessionId private val sessionId: String,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
||||||
|
|
||||||
@ -103,9 +103,9 @@ internal class DefaultFileService @Inject constructor(private val context: Conte
|
|||||||
return when (downloadMode) {
|
return when (downloadMode) {
|
||||||
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
FileService.DownloadMode.FOR_INTERNAL_USE -> {
|
||||||
// Create dir tree (MF stands for Matrix File):
|
// Create dir tree (MF stands for Matrix File):
|
||||||
// <cache>/MF/<md5(userId)>/<md5(id)>/
|
// <cache>/MF/<sessionId>/<md5(id)>/
|
||||||
val tmpFolderRoot = File(context.cacheDir, "MF")
|
val tmpFolderRoot = File(context.cacheDir, "MF")
|
||||||
val tmpFolderUser = File(tmpFolderRoot, userMd5)
|
val tmpFolderUser = File(tmpFolderRoot, sessionId)
|
||||||
File(tmpFolderUser, id.md5())
|
File(tmpFolderUser, id.md5())
|
||||||
}
|
}
|
||||||
FileService.DownloadMode.TO_EXPORT -> {
|
FileService.DownloadMode.TO_EXPORT -> {
|
||||||
|
@ -45,6 +45,8 @@ import im.vector.matrix.android.api.session.user.UserService
|
|||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer
|
||||||
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -76,24 +78,26 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
|||||||
private val secureStorageService: Lazy<SecureStorageService>,
|
private val secureStorageService: Lazy<SecureStorageService>,
|
||||||
private val syncThreadProvider: Provider<SyncThread>,
|
private val syncThreadProvider: Provider<SyncThread>,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
private val syncTokenStore: SyncTokenStore,
|
||||||
|
private val syncTaskSequencer: SyncTaskSequencer,
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
||||||
: Session,
|
: Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
GroupService by groupService.get(),
|
GroupService by groupService.get(),
|
||||||
UserService by userService.get(),
|
UserService by userService.get(),
|
||||||
CryptoService by cryptoService.get(),
|
CryptoService by cryptoService.get(),
|
||||||
SignOutService by signOutService.get(),
|
SignOutService by signOutService.get(),
|
||||||
FilterService by filterService.get(),
|
FilterService by filterService.get(),
|
||||||
PushRuleService by pushRuleService.get(),
|
PushRuleService by pushRuleService.get(),
|
||||||
PushersService by pushersService.get(),
|
PushersService by pushersService.get(),
|
||||||
FileService by fileService.get(),
|
FileService by fileService.get(),
|
||||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
InitialSyncProgressService by initialSyncProgressService.get(),
|
||||||
SecureStorageService by secureStorageService.get(),
|
SecureStorageService by secureStorageService.get(),
|
||||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get() {
|
HomeServerCapabilitiesService by homeServerCapabilitiesService.get() {
|
||||||
|
|
||||||
private var isOpen = false
|
private var isOpen = false
|
||||||
|
|
||||||
@ -149,12 +153,17 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
|||||||
cryptoService.get().close()
|
cryptoService.get().close()
|
||||||
isOpen = false
|
isOpen = false
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
|
syncTaskSequencer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun syncState(): LiveData<SyncState> {
|
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||||
return getSyncThread().liveState()
|
return getSyncThread().liveState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hasAlreadySynced(): Boolean {
|
||||||
|
return syncTokenStore.getLastToken() != null
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSyncThread(): SyncThread {
|
private fun getSyncThread(): SyncThread {
|
||||||
return syncThread ?: syncThreadProvider.get().also {
|
return syncThread ?: syncThreadProvider.get().also {
|
||||||
syncThread = it
|
syncThread = it
|
||||||
@ -164,23 +173,14 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
|||||||
override fun clearCache(callback: MatrixCallback<Unit>) {
|
override fun clearCache(callback: MatrixCallback<Unit>) {
|
||||||
stopSync()
|
stopSync()
|
||||||
stopAnyBackgroundSync()
|
stopAnyBackgroundSync()
|
||||||
cacheService.get().clearCache(object : MatrixCallback<Unit> {
|
liveEntityObservers.forEach { it.cancelProcess() }
|
||||||
override fun onSuccess(data: Unit) {
|
cacheService.get().clearCache(callback)
|
||||||
startSync(true)
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
startSync(true)
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onGlobalError(globalError: GlobalError) {
|
fun onGlobalError(globalError: GlobalError) {
|
||||||
if (globalError is GlobalError.InvalidToken
|
if (globalError is GlobalError.InvalidToken
|
||||||
&& globalError.softLogout) {
|
&& globalError.softLogout) {
|
||||||
// Mark the token has invalid
|
// Mark the token has invalid
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
sessionParamsStore.setTokenInvalid(myUserId)
|
sessionParamsStore.setTokenInvalid(myUserId)
|
||||||
|
@ -46,6 +46,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
|||||||
import im.vector.matrix.android.internal.session.user.UserModule
|
import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
|
||||||
@Component(dependencies = [MatrixComponent::class],
|
@Component(dependencies = [MatrixComponent::class],
|
||||||
modules = [
|
modules = [
|
||||||
@ -69,6 +70,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||||||
@SessionScope
|
@SessionScope
|
||||||
internal interface SessionComponent {
|
internal interface SessionComponent {
|
||||||
|
|
||||||
|
fun coroutineDispatchers(): MatrixCoroutineDispatchers
|
||||||
|
|
||||||
fun session(): Session
|
fun session(): Session
|
||||||
|
|
||||||
fun syncTask(): SyncTask
|
fun syncTask(): SyncTask
|
||||||
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.InitialSyncProgressService
|
|||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
||||||
|
import im.vector.matrix.android.internal.auth.createSessionId
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
|
||||||
import im.vector.matrix.android.internal.di.*
|
import im.vector.matrix.android.internal.di.*
|
||||||
@ -54,8 +55,7 @@ internal abstract class SessionModule {
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
|
internal fun getKeyAlias(userMd5: String) = "session_db_$userMd5"
|
||||||
internal const val DB_ALIAS_PREFIX = "session_db_"
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@ -83,11 +83,26 @@ internal abstract class SessionModule {
|
|||||||
return userId.md5()
|
return userId.md5()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@SessionId
|
||||||
|
@Provides
|
||||||
|
fun providesSessionId(credentials: Credentials): String {
|
||||||
|
return createSessionId(credentials.userId, credentials.deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@UserCacheDirectory
|
@UserCacheDirectory
|
||||||
fun providesFilesDir(@UserMd5 userMd5: String, context: Context): File {
|
fun providesFilesDir(@UserMd5 userMd5: String,
|
||||||
return File(context.filesDir, userMd5)
|
@SessionId sessionId: String,
|
||||||
|
context: Context): File {
|
||||||
|
// Temporary code for migration
|
||||||
|
val old = File(context.filesDir, userMd5)
|
||||||
|
if (old.exists()) {
|
||||||
|
old.renameTo(File(context.filesDir, sessionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
return File(context.filesDir, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -20,11 +20,16 @@ import androidx.lifecycle.LiveData
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.group.Group
|
import im.vector.matrix.android.api.session.group.Group
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
|
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.query.process
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService {
|
internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService {
|
||||||
@ -33,10 +38,30 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveGroupSummaries(): LiveData<List<GroupSummary>> {
|
override fun getGroupSummary(groupId: String): GroupSummary? {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.fetchCopyMap(
|
||||||
{ realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> GroupSummaryEntity.where(realm, groupId).findFirst() },
|
||||||
|
{ it, _ -> it.asDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary> {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ groupSummariesQuery(it, groupSummaryQueryParams) },
|
||||||
{ it.asDomain() }
|
{ it.asDomain() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ groupSummariesQuery(it, groupSummaryQueryParams) },
|
||||||
|
{ it.asDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
|
||||||
|
return GroupSummaryEntity.where(realm)
|
||||||
|
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
|
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import androidx.work.WorkManager
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.database.model.GroupEntity
|
import im.vector.matrix.android.internal.database.model.GroupEntity
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -31,6 +32,7 @@ import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWor
|
|||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
|
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
|
||||||
@ -49,14 +51,19 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
|
|||||||
.mapNotNull { results[it] }
|
.mapNotNull { results[it] }
|
||||||
|
|
||||||
fetchGroupsData(modifiedGroupEntity
|
fetchGroupsData(modifiedGroupEntity
|
||||||
.filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
|
.filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
|
||||||
.map { it.groupId }
|
.map { it.groupId }
|
||||||
.toList())
|
.toList())
|
||||||
|
|
||||||
deleteGroups(modifiedGroupEntity
|
modifiedGroupEntity
|
||||||
.filter { it.membership == Membership.LEAVE }
|
.filter { it.membership == Membership.LEAVE }
|
||||||
.map { it.groupId }
|
.map { it.groupId }
|
||||||
.toList())
|
.toList()
|
||||||
|
.also {
|
||||||
|
observerScope.launch {
|
||||||
|
deleteGroups(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchGroupsData(groupIds: List<String>) {
|
private fun fetchGroupsData(groupIds: List<String>) {
|
||||||
@ -77,12 +84,9 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
|
|||||||
/**
|
/**
|
||||||
* Delete the GroupSummaryEntity of left groups
|
* Delete the GroupSummaryEntity of left groups
|
||||||
*/
|
*/
|
||||||
private fun deleteGroups(groupIds: List<String>) {
|
private suspend fun deleteGroups(groupIds: List<String>) = awaitTransaction(monarchy.realmConfiguration) { realm ->
|
||||||
monarchy
|
GroupSummaryEntity.where(realm, groupIds)
|
||||||
.writeAsync { realm ->
|
.findAll()
|
||||||
GroupSummaryEntity.where(realm, groupIds)
|
.deleteAllFromRealm()
|
||||||
.findAll()
|
|
||||||
.deleteAllFromRealm()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ internal class DefaultPusherService @Inject constructor(private val context: Con
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun livePushers(): LiveData<List<Pusher>> {
|
override fun getPushersLive(): LiveData<List<Pusher>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm -> PusherEntity.where(realm) },
|
{ realm -> PusherEntity.where(realm) },
|
||||||
{ it.asDomain() }
|
{ it.asDomain() }
|
||||||
|
@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
|
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
@ -30,7 +31,9 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
|||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.query.findByAlias
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.query.process
|
||||||
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
@ -38,7 +41,9 @@ import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
|||||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
|
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
|
||||||
@ -69,30 +74,66 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy
|
||||||
{ realm ->
|
.fetchCopyMap({
|
||||||
RoomSummaryEntity.where(realm)
|
if (roomIdOrAlias.startsWith("!")) {
|
||||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
// It's a roomId
|
||||||
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
RoomSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst()
|
||||||
},
|
} else {
|
||||||
|
// Assume it's a room alias
|
||||||
|
RoomSummaryEntity.findByAlias(it, roomIdOrAlias)
|
||||||
|
}
|
||||||
|
}, { entity, _ ->
|
||||||
|
roomSummaryMapper.map(entity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ roomSummariesQuery(it, queryParams) },
|
||||||
{ roomSummaryMapper.map(it) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveBreadcrumbs(): LiveData<List<RoomSummary>> {
|
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm ->
|
{ roomSummariesQuery(it, queryParams) },
|
||||||
RoomSummaryEntity.where(realm)
|
|
||||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
|
||||||
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
|
||||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
|
||||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
|
||||||
},
|
|
||||||
{ roomSummaryMapper.map(it) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
||||||
|
val query = RoomSummaryEntity.where(realm)
|
||||||
|
query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
|
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||||
|
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
|
query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBreadcrumbs(): List<RoomSummary> {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ breadcrumbsQuery(it) },
|
||||||
|
{ roomSummaryMapper.map(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBreadcrumbsLive(): LiveData<List<RoomSummary>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ breadcrumbsQuery(it) },
|
||||||
|
{ roomSummaryMapper.map(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun breadcrumbsQuery(realm: Realm): RealmQuery<RoomSummaryEntity> {
|
||||||
|
return RoomSummaryEntity.where(realm)
|
||||||
|
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||||
|
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||||
|
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||||
|
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRoomDisplayed(roomId: String): Cancelable {
|
override fun onRoomDisplayed(roomId: String): Cancelable {
|
||||||
return updateBreadcrumbsTask
|
return updateBreadcrumbsTask
|
||||||
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
|
||||||
|
@ -23,11 +23,10 @@ import im.vector.matrix.android.internal.database.model.EventEntity
|
|||||||
import im.vector.matrix.android.internal.database.query.types
|
import im.vector.matrix.android.internal.database.query.types
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -39,8 +38,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
private val task: EventRelationsAggregationTask,
|
private val task: EventRelationsAggregationTask) :
|
||||||
private val taskExecutor: TaskExecutor) :
|
|
||||||
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query<EventEntity> {
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
@ -63,6 +61,8 @@ internal class EventRelationsAggregationUpdater @Inject constructor(@SessionData
|
|||||||
insertedDomains,
|
insertedDomains,
|
||||||
userId
|
userId
|
||||||
)
|
)
|
||||||
task.configureWith(params).executeBy(taskExecutor)
|
observerScope.launch {
|
||||||
|
task.execute(params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,11 +212,12 @@ internal interface RoomAPI {
|
|||||||
/**
|
/**
|
||||||
* Join the given room.
|
* Join the given room.
|
||||||
*
|
*
|
||||||
* @param roomId the room id
|
* @param roomIdOrAlias the room id or alias
|
||||||
|
* @param server_name the servers to attempt to join the room through
|
||||||
* @param params the request body
|
* @param params the request body
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
|
||||||
fun join(@Path("roomId") roomId: String,
|
fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
|
||||||
@Query("server_name") viaServers: List<String>,
|
@Query("server_name") viaServers: List<String>,
|
||||||
@Body params: Map<String, String?>): Call<Unit>
|
@Body params: Map<String, String?>): Call<Unit>
|
||||||
|
|
||||||
|
@ -20,10 +20,9 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
|
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
@ -47,19 +46,15 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
|
|||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
}
|
}
|
||||||
val roomMembers = RoomMembers(realm, roomId)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
val members = roomMembers.queryRoomMembersEvent().findAll()
|
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
|
||||||
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
|
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
|
||||||
if (members.size == 1) {
|
if (members.size == 1) {
|
||||||
res = members.firstOrNull()?.toRoomMember()?.avatarUrl
|
res = members.firstOrNull()?.avatarUrl
|
||||||
} else if (members.size == 2) {
|
} else if (members.size == 2) {
|
||||||
val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, userId).findFirst()
|
val firstOtherMember = members.where().notEqualTo(RoomMemberEntityFields.USER_ID, userId).findFirst()
|
||||||
res = firstOtherMember?.toRoomMember()?.avatarUrl
|
res = firstOtherMember?.avatarUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun EventEntity?.toRoomMember(): RoomMember? {
|
|
||||||
return ContentMapper.map(this?.content).toModel<RoomMember>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
|
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
|
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
|
||||||
@ -35,6 +36,7 @@ internal interface RoomFactory {
|
|||||||
fun create(roomId: String): Room
|
fun create(roomId: String): Room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy,
|
internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy,
|
||||||
private val roomSummaryMapper: RoomSummaryMapper,
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
|
@ -24,10 +24,11 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.*
|
||||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
@ -38,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
|||||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
||||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.createObject
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String,
|
internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String,
|
||||||
@ -52,7 +52,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
|||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
EventType.STATE_ROOM_MEMBER,
|
EventType.STATE_ROOM_MEMBER,
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
@ -69,9 +69,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
|||||||
roomSummary: RoomSyncSummary? = null,
|
roomSummary: RoomSyncSummary? = null,
|
||||||
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
||||||
updateMembers: Boolean = false) {
|
updateMembers: Boolean = false) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||||
?: realm.createObject(roomId)
|
|
||||||
|
|
||||||
if (roomSummary != null) {
|
if (roomSummary != null) {
|
||||||
if (roomSummary.heroes.isNotEmpty()) {
|
if (roomSummary.heroes.isNotEmpty()) {
|
||||||
roomSummaryEntity.heroes.clear()
|
roomSummaryEntity.heroes.clear()
|
||||||
@ -93,12 +91,13 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
|||||||
|
|
||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
||||||
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
||||||
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
|
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
|
||||||
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
|
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
|
||||||
|
val encryptionEvent = EventEntity.where(realm, roomId, EventType.ENCRYPTION).prev()
|
||||||
|
|
||||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||||
// avoid this call if we are sure there are unread events
|
// avoid this call if we are sure there are unread events
|
||||||
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
||||||
|
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||||
@ -107,18 +106,20 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
|||||||
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
|
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
|
||||||
?.canonicalAlias
|
?.canonicalAlias
|
||||||
|
|
||||||
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases ?: emptyList()
|
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
||||||
|
?: emptyList()
|
||||||
roomSummaryEntity.aliases.clear()
|
roomSummaryEntity.aliases.clear()
|
||||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||||
|
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
||||||
|
|
||||||
if (updateMembers) {
|
if (updateMembers) {
|
||||||
val otherRoomMembers = RoomMembers(realm, roomId)
|
val otherRoomMembers = RoomMembers(realm, roomId)
|
||||||
.queryRoomMembersEvent()
|
.queryRoomMembersEvent()
|
||||||
.notEqualTo(EventEntityFields.STATE_KEY, userId)
|
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { it.stateKey }
|
.map { it.userId }
|
||||||
|
|
||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
||||||
|
@ -20,7 +20,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
|
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||||
import im.vector.matrix.android.internal.database.RealmQueryLatch
|
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAcco
|
|||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -53,13 +54,12 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
|
|||||||
}
|
}
|
||||||
val roomId = createRoomResponse.roomId!!
|
val roomId = createRoomResponse.roomId!!
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
||||||
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
|
||||||
realm.where(RoomEntity::class.java)
|
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
} catch (exception: Exception) {
|
realm.where(RoomEntity::class.java)
|
||||||
|
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||||
|
}
|
||||||
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw CreateRoomFailure.CreatedWithTimeout
|
throw CreateRoomFailure.CreatedWithTimeout
|
||||||
}
|
}
|
||||||
if (params.isDirect()) {
|
if (params.isDirect()) {
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
@ -30,9 +31,9 @@ import im.vector.matrix.android.internal.database.query.types
|
|||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase
|
internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase
|
||||||
@ -51,21 +52,21 @@ internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase
|
|||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
.also {
|
.also {
|
||||||
handleRoomCreateEvents(it)
|
observerScope.launch {
|
||||||
|
handleRoomCreateEvents(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRoomCreateEvents(createEvents: List<Event>) = Realm.getInstance(realmConfiguration).use {
|
private suspend fun handleRoomCreateEvents(createEvents: List<Event>) = awaitTransaction(realmConfiguration) { realm ->
|
||||||
it.executeTransactionAsync { realm ->
|
for (event in createEvents) {
|
||||||
for (event in createEvents) {
|
val createRoomContent = event.getClearContent().toModel<RoomCreateContent>()
|
||||||
val createRoomContent = event.getClearContent().toModel<RoomCreateContent>()
|
val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: continue
|
||||||
val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: continue
|
|
||||||
|
|
||||||
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
|
val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst()
|
||||||
?: RoomSummaryEntity(predecessorRoomId)
|
?: RoomSummaryEntity(predecessorRoomId)
|
||||||
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
|
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED
|
||||||
realm.insertOrUpdate(predecessorRoomSummary)
|
realm.insertOrUpdate(predecessorRoomSummary)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,18 +21,23 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
|
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
|
||||||
|
import im.vector.matrix.android.internal.query.process
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
|
||||||
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
@ -58,29 +63,44 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomMember(userId: String): RoomMember? {
|
override fun getRoomMember(userId: String): RoomMember? {
|
||||||
val eventEntity = monarchy.fetchCopied {
|
val roomMemberEntity = monarchy.fetchCopied {
|
||||||
RoomMembers(it, roomId).queryRoomMemberEvent(userId).findFirst()
|
RoomMembers(it, roomId).getLastRoomMember(userId)
|
||||||
}
|
}
|
||||||
return eventEntity?.asDomain()?.content.toModel()
|
return roomMemberEntity?.asDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomMemberIdsLive(): LiveData<List<String>> {
|
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.fetchAllMappedSync(
|
||||||
{
|
{
|
||||||
RoomMembers(it, roomId).queryRoomMembersEvent()
|
roomMembersQuery(it, queryParams)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
it.stateKey!!
|
it.asDomain()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{
|
||||||
|
roomMembersQuery(it, queryParams)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
it.asDomain()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberEntity> {
|
||||||
|
return RoomMembers(realm, roomId).queryRoomMembersEvent()
|
||||||
|
.process(RoomMemberEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||||
|
.process(RoomMemberEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getNumberOfJoinedMembers(): Int {
|
override fun getNumberOfJoinedMembers(): Int {
|
||||||
var result = 0
|
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||||
monarchy.runTransactionSync {
|
RoomMembers(it, roomId).getNumberOfJoinedMembers()
|
||||||
result = RoomMembers(it, roomId).getNumberOfJoinedMembers()
|
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
@ -18,15 +18,14 @@ package im.vector.matrix.android.internal.session.room.membership
|
|||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor
|
||||||
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
||||||
import im.vector.matrix.android.internal.database.helper.updateSenderData
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
@ -44,7 +43,9 @@ internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit>
|
|||||||
internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI,
|
internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||||
|
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||||
|
private val timelineEventSenderVisitor: TimelineEventSenderVisitor
|
||||||
) : LoadRoomMembersTask {
|
) : LoadRoomMembersTask {
|
||||||
|
|
||||||
override suspend fun execute(params: LoadRoomMembersTask.Params) {
|
override suspend fun execute(params: LoadRoomMembersTask.Params) {
|
||||||
@ -66,12 +67,11 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
|
|||||||
|
|
||||||
for (roomMemberEvent in response.roomMemberEvents) {
|
for (roomMemberEvent in response.roomMemberEvents) {
|
||||||
roomEntity.addStateEvent(roomMemberEvent)
|
roomEntity.addStateEvent(roomMemberEvent)
|
||||||
UserEntityFactory.createOrNull(roomMemberEvent)?.also {
|
roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
|
||||||
realm.insertOrUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
timelineEventSenderVisitor.clear()
|
||||||
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
|
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
|
||||||
it.updateSenderData()
|
timelineEventSenderVisitor.visit(it)
|
||||||
}
|
}
|
||||||
roomEntity.areAllMembersLoaded = true
|
roomEntity.areAllMembersLoaded = true
|
||||||
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
|
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
|
||||||
|
@ -23,9 +23,10 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.*
|
import im.vector.matrix.android.api.session.room.model.*
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -62,7 +63,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
}
|
}
|
||||||
|
|
||||||
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
|
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev()
|
||||||
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
|
||||||
if (!name.isNullOrEmpty()) {
|
if (!name.isNullOrEmpty()) {
|
||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
@ -75,43 +76,46 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
}
|
}
|
||||||
|
|
||||||
val roomMembers = RoomMembers(realm, roomId)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
val loadedMembers = roomMembers.queryRoomMembersEvent().findAll()
|
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()
|
||||||
|
|
||||||
if (roomEntity?.membership == Membership.INVITE) {
|
if (roomEntity?.membership == Membership.INVITE) {
|
||||||
val inviteMeEvent = roomMembers.queryRoomMemberEvent(userId).findFirst()
|
val inviteMeEvent = roomMembers.getLastStateEvent(userId)
|
||||||
val inviterId = inviteMeEvent?.sender
|
val inviterId = inviteMeEvent?.sender
|
||||||
name = if (inviterId != null) {
|
name = if (inviterId != null) {
|
||||||
val inviterMemberEvent = loadedMembers.where()
|
activeMembers.where()
|
||||||
.equalTo(EventEntityFields.STATE_KEY, inviterId)
|
.equalTo(RoomMemberEntityFields.USER_ID, inviterId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
inviterMemberEvent?.toRoomMember()?.displayName
|
?.displayName
|
||||||
} else {
|
} else {
|
||||||
context.getString(R.string.room_displayname_room_invite)
|
context.getString(R.string.room_displayname_room_invite)
|
||||||
}
|
}
|
||||||
} else if (roomEntity?.membership == Membership.JOIN) {
|
} else if (roomEntity?.membership == Membership.JOIN) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
val otherMembersSubset: List<EventEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
val otherMembersSubset: List<RoomMemberEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||||
roomSummary.heroes.mapNotNull {
|
roomSummary.heroes.mapNotNull { userId ->
|
||||||
roomMembers.getStateEvent(it)
|
roomMembers.getLastRoomMember(userId)?.takeIf {
|
||||||
|
it.membership == Membership.INVITE || it.membership == Membership.JOIN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loadedMembers.where()
|
activeMembers.where()
|
||||||
.notEqualTo(EventEntityFields.STATE_KEY, userId)
|
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
|
||||||
.limit(3)
|
.limit(3)
|
||||||
.findAll()
|
.findAll()
|
||||||
|
.createSnapshot()
|
||||||
}
|
}
|
||||||
val otherMembersCount = roomMembers.getNumberOfMembers() - 1
|
val otherMembersCount = otherMembersSubset.count()
|
||||||
name = when (otherMembersCount) {
|
name = when (otherMembersCount) {
|
||||||
0 -> context.getString(R.string.room_displayname_empty_room)
|
0 -> context.getString(R.string.room_displayname_empty_room)
|
||||||
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
||||||
2 -> context.getString(R.string.room_displayname_two_members,
|
2 -> context.getString(R.string.room_displayname_two_members,
|
||||||
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
||||||
)
|
)
|
||||||
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
|
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1,
|
roomMembers.getNumberOfJoinedMembers() - 1,
|
||||||
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1)
|
roomMembers.getNumberOfJoinedMembers() - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@doWithRealm
|
return@doWithRealm
|
||||||
@ -119,19 +123,14 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
|
|||||||
return name ?: roomId
|
return name ?: roomId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveRoomMemberName(eventEntity: EventEntity?,
|
private fun resolveRoomMemberName(roomMember: RoomMemberEntity?,
|
||||||
roomMembers: RoomMembers): String? {
|
roomMembers: RoomMembers): String? {
|
||||||
if (eventEntity == null) return null
|
if (roomMember == null) return null
|
||||||
val roomMember = eventEntity.toRoomMember() ?: return null
|
|
||||||
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
|
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
|
||||||
return if (isUnique) {
|
return if (isUnique) {
|
||||||
roomMember.displayName
|
roomMember.displayName
|
||||||
} else {
|
} else {
|
||||||
"${roomMember.displayName} (${eventEntity.stateKey})"
|
"${roomMember.displayName} (${roomMember.userId})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun EventEntity?.toRoomMember(): RoomMember? {
|
|
||||||
return ContentMapper.map(this?.content).toModel<RoomMember>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.room.membership
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
|
|
||||||
|
internal object RoomMemberEntityFactory {
|
||||||
|
|
||||||
|
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity {
|
||||||
|
val primaryKey = "${roomId}_$userId"
|
||||||
|
return RoomMemberEntity(
|
||||||
|
primaryKey = primaryKey,
|
||||||
|
userId = userId,
|
||||||
|
roomId = roomId,
|
||||||
|
displayName = roomMember.displayName ?: "",
|
||||||
|
avatarUrl = roomMember.avatarUrl ?: ""
|
||||||
|
).apply {
|
||||||
|
membership = roomMember.membership
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.room.membership
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
|
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
||||||
|
import io.realm.Realm
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class RoomMemberEventHandler @Inject constructor() {
|
||||||
|
|
||||||
|
fun handle(realm: Realm, roomId: String, event: Event): Boolean {
|
||||||
|
if (event.type != EventType.STATE_ROOM_MEMBER) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val roomMember = event.content.toModel<RoomMemberContent>() ?: return false
|
||||||
|
val userId = event.stateKey ?: return false
|
||||||
|
val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember)
|
||||||
|
realm.insertOrUpdate(roomMemberEntity)
|
||||||
|
if (roomMember.membership in Membership.activeMemberships()) {
|
||||||
|
val userEntity = UserEntityFactory.create(userId, roomMember)
|
||||||
|
realm.insertOrUpdate(userEntity)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -17,12 +17,10 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.membership
|
package im.vector.matrix.android.internal.session.room.membership
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
@ -42,19 +40,18 @@ internal class RoomMembers(private val realm: Realm,
|
|||||||
RoomSummaryEntity.where(realm, roomId).findFirst()
|
RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStateEvent(userId: String): EventEntity? {
|
fun getLastStateEvent(userId: String): EventEntity? {
|
||||||
return EventEntity
|
return EventEntity
|
||||||
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
||||||
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
|
||||||
.equalTo(EventEntityFields.STATE_KEY, userId)
|
.equalTo(EventEntityFields.STATE_KEY, userId)
|
||||||
|
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(userId: String): RoomMember? {
|
fun getLastRoomMember(userId: String): RoomMemberEntity? {
|
||||||
return getStateEvent(userId)
|
return RoomMemberEntity
|
||||||
?.let {
|
.where(realm, roomId, userId)
|
||||||
it.asDomain().content?.toModel<RoomMember>()
|
.findFirst()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isUniqueDisplayName(displayName: String?): Boolean {
|
fun isUniqueDisplayName(displayName: String?): Boolean {
|
||||||
@ -69,36 +66,37 @@ internal class RoomMembers(private val realm: Realm,
|
|||||||
.size == 1
|
.size == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun queryRoomMembersEvent(): RealmQuery<EventEntity> {
|
fun queryRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||||
return EventEntity
|
return RoomMemberEntity.where(realm, roomId)
|
||||||
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
|
|
||||||
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
|
||||||
.isNotNull(EventEntityFields.STATE_KEY)
|
|
||||||
.distinct(EventEntityFields.STATE_KEY)
|
|
||||||
.isNotNull(EventEntityFields.CONTENT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun queryJoinedRoomMembersEvent(): RealmQuery<EventEntity> {
|
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||||
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun queryInvitedRoomMembersEvent(): RealmQuery<EventEntity> {
|
|
||||||
return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun queryRoomMemberEvent(userId: String): RealmQuery<EventEntity> {
|
|
||||||
return queryRoomMembersEvent()
|
return queryRoomMembersEvent()
|
||||||
.equalTo(EventEntityFields.STATE_KEY, userId)
|
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||||
|
return queryRoomMembersEvent()
|
||||||
|
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
|
||||||
|
return queryRoomMembersEvent()
|
||||||
|
.beginGroup()
|
||||||
|
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
|
||||||
|
.or()
|
||||||
|
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
|
.endGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers(): Int {
|
fun getNumberOfJoinedMembers(): Int {
|
||||||
return roomSummary?.joinedMembersCount
|
return roomSummary?.joinedMembersCount
|
||||||
?: queryJoinedRoomMembersEvent().findAll().size
|
?: queryJoinedRoomMembersEvent().findAll().size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNumberOfInvitedMembers(): Int {
|
fun getNumberOfInvitedMembers(): Int {
|
||||||
return roomSummary?.invitedMembersCount
|
return roomSummary?.invitedMembersCount
|
||||||
?: queryInvitedRoomMembersEvent().findAll().size
|
?: queryInvitedRoomMembersEvent().findAll().size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNumberOfMembers(): Int {
|
fun getNumberOfMembers(): Int {
|
||||||
@ -111,7 +109,7 @@ internal class RoomMembers(private val realm: Realm,
|
|||||||
* @return a roomMember id list of joined or invited members.
|
* @return a roomMember id list of joined or invited members.
|
||||||
*/
|
*/
|
||||||
fun getActiveRoomMemberIds(): List<String> {
|
fun getActiveRoomMemberIds(): List<String> {
|
||||||
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN || it.membership == Membership.INVITE }
|
return queryActiveRoomMembersEvent().findAll().map { it.userId }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,21 +118,6 @@ internal class RoomMembers(private val realm: Realm,
|
|||||||
* @return a roomMember id list of joined members.
|
* @return a roomMember id list of joined members.
|
||||||
*/
|
*/
|
||||||
fun getJoinedRoomMemberIds(): List<String> {
|
fun getJoinedRoomMemberIds(): List<String> {
|
||||||
return getRoomMemberIdsFiltered { it.membership == Membership.JOIN }
|
return queryJoinedRoomMembersEvent().findAll().map { it.userId }
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================================
|
|
||||||
* Private
|
|
||||||
* ========================================================================================== */
|
|
||||||
|
|
||||||
private fun getRoomMemberIdsFiltered(predicate: (RoomMember) -> Boolean): List<String> {
|
|
||||||
return RoomMembers(realm, roomId)
|
|
||||||
.queryRoomMembersEvent()
|
|
||||||
.findAll()
|
|
||||||
.map { it.asDomain() }
|
|
||||||
.associateBy { it.stateKey!! }
|
|
||||||
.filterValues { predicate(it.content.toModel<RoomMember>()!!) }
|
|
||||||
.keys
|
|
||||||
.toList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.membership.joining
|
package im.vector.matrix.android.internal.session.room.membership.joining
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure
|
import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure
|
||||||
import im.vector.matrix.android.internal.database.RealmQueryLatch
|
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomEntityFields
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
|
|||||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -46,18 +47,16 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room
|
|||||||
executeRequest<Unit> {
|
executeRequest<Unit> {
|
||||||
apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason))
|
apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason))
|
||||||
}
|
}
|
||||||
val roomId = params.roomId
|
|
||||||
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
|
||||||
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
|
|
||||||
realm.where(RoomEntity::class.java)
|
|
||||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
} catch (exception: Exception) {
|
realm.where(RoomEntity::class.java)
|
||||||
|
.equalTo(RoomEntityFields.ROOM_ID, params.roomId)
|
||||||
|
}
|
||||||
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw JoinRoomFailure.JoinedWithTimeout
|
throw JoinRoomFailure.JoinedWithTimeout
|
||||||
}
|
}
|
||||||
setReadMarkers(roomId)
|
setReadMarkers(params.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun setReadMarkers(roomId: String) {
|
private suspend fun setReadMarkers(roomId: String) {
|
||||||
|
@ -23,11 +23,10 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
|
|||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.types
|
import im.vector.matrix.android.internal.database.query.types
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -36,8 +35,7 @@ import javax.inject.Inject
|
|||||||
* As it will actually delete the content, it should be called last in the list of listener.
|
* As it will actually delete the content, it should be called last in the list of listener.
|
||||||
*/
|
*/
|
||||||
internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
private val pruneEventTask: PruneEventTask,
|
private val pruneEventTask: PruneEventTask) :
|
||||||
private val taskExecutor: TaskExecutor) :
|
|
||||||
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query<EventEntity> { EventEntity.types(it, listOf(EventType.REDACTION)) }
|
override val query = Monarchy.Query<EventEntity> { EventEntity.types(it, listOf(EventType.REDACTION)) }
|
||||||
@ -50,7 +48,9 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat
|
|||||||
.mapNotNull { results[it]?.asDomain() }
|
.mapNotNull { results[it]?.asDomain() }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
val params = PruneEventTask.Params(insertedDomains)
|
observerScope.launch {
|
||||||
pruneEventTask.configureWith(params).executeBy(taskExecutor)
|
val params = PruneEventTask.Params(insertedDomains)
|
||||||
|
pruneEventTask.execute(params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
import im.vector.matrix.android.internal.database.helper.updateSenderData
|
import im.vector.matrix.android.internal.database.helper.TimelineEventSenderVisitor
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.EventMapper
|
import im.vector.matrix.android.internal.database.mapper.EventMapper
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
@ -41,7 +41,8 @@ internal interface PruneEventTask : Task<PruneEventTask.Params, Unit> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask {
|
internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy,
|
||||||
|
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) : PruneEventTask {
|
||||||
|
|
||||||
override suspend fun execute(params: PruneEventTask.Params) {
|
override suspend fun execute(params: PruneEventTask.Params) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
@ -65,12 +66,14 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
|
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val allowedKeys = computeAllowedKeys(eventToPrune.type)
|
val typeToPrune = eventToPrune.type
|
||||||
|
val stateKey = eventToPrune.stateKey
|
||||||
|
val allowedKeys = computeAllowedKeys(typeToPrune)
|
||||||
if (allowedKeys.isNotEmpty()) {
|
if (allowedKeys.isNotEmpty()) {
|
||||||
val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) }
|
val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) }
|
||||||
eventToPrune.content = ContentMapper.map(prunedContent)
|
eventToPrune.content = ContentMapper.map(prunedContent)
|
||||||
} else {
|
} else {
|
||||||
when (eventToPrune.type) {
|
when (typeToPrune) {
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||||
@ -94,21 +97,20 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) {
|
if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) {
|
||||||
|
timelineEventSenderVisitor.clear(roomId = eventToPrune.roomId, senderId = stateKey)
|
||||||
val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId)
|
val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId)
|
||||||
for (timelineEvent in timelineEventsToUpdate) {
|
timelineEventSenderVisitor.visit(timelineEventsToUpdate)
|
||||||
timelineEvent.updateSenderData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeAllowedKeys(type: String): List<String> {
|
private fun computeAllowedKeys(type: String): List<String> {
|
||||||
// Add filtered content, allowed keys in content depends on the event type
|
// Add filtered content, allowed keys in content depends on the event type
|
||||||
return when (type) {
|
return when (type) {
|
||||||
EventType.STATE_ROOM_MEMBER -> listOf("membership")
|
EventType.STATE_ROOM_MEMBER -> listOf("membership")
|
||||||
EventType.STATE_ROOM_CREATE -> listOf("creator")
|
EventType.STATE_ROOM_CREATE -> listOf("creator")
|
||||||
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
|
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
|
||||||
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
|
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
|
||||||
"users_default",
|
"users_default",
|
||||||
"events",
|
"events",
|
||||||
"events_default",
|
"events_default",
|
||||||
@ -117,10 +119,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
"kick",
|
"kick",
|
||||||
"redact",
|
"redact",
|
||||||
"invite")
|
"invite")
|
||||||
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
||||||
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> listOf("alias")
|
||||||
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,16 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
|
|||||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
|
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
|
||||||
|
return monarchy.fetchCopyMap(
|
||||||
|
{ EventAnnotationsSummaryEntity.where(it, eventId).findFirst() },
|
||||||
|
{ entity, _ ->
|
||||||
|
entity.asDomain()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
{ EventAnnotationsSummaryEntity.where(it, eventId) },
|
{ EventAnnotationsSummaryEntity.where(it, eventId) },
|
||||||
{ it.asDomain() }
|
{ it.asDomain() }
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.send.pills
|
package im.vector.matrix.android.internal.session.room.send.pills
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
|
import im.vector.matrix.android.api.session.room.send.MatrixItemSpan
|
||||||
|
|
||||||
internal data class MentionLinkSpec(
|
internal data class MentionLinkSpec(
|
||||||
val span: UserMentionSpan,
|
val span: MatrixItemSpan,
|
||||||
val start: Int,
|
val start: Int,
|
||||||
val end: Int
|
val end: Int
|
||||||
)
|
)
|
||||||
|
@ -16,15 +16,13 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.send.pills
|
package im.vector.matrix.android.internal.session.room.send.pills
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import im.vector.matrix.android.api.session.room.send.UserMentionSpan
|
import im.vector.matrix.android.api.session.room.send.MatrixItemSpan
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to detect special span in CharSequence and turn them into
|
* Utility class to detect special span in CharSequence and turn them into
|
||||||
* formatted text to send them as a Matrix messages.
|
* formatted text to send them as a Matrix messages.
|
||||||
*
|
|
||||||
* For now only support UserMentionSpans (TODO rooms, room aliases, etc...)
|
|
||||||
*/
|
*/
|
||||||
internal class TextPillsUtils @Inject constructor(
|
internal class TextPillsUtils @Inject constructor(
|
||||||
private val mentionLinkSpecComparator: MentionLinkSpecComparator
|
private val mentionLinkSpecComparator: MentionLinkSpecComparator
|
||||||
@ -49,7 +47,7 @@ internal class TextPillsUtils @Inject constructor(
|
|||||||
private fun transformPills(text: CharSequence, template: String): String? {
|
private fun transformPills(text: CharSequence, template: String): String? {
|
||||||
val spannableString = SpannableString.valueOf(text)
|
val spannableString = SpannableString.valueOf(text)
|
||||||
val pills = spannableString
|
val pills = spannableString
|
||||||
?.getSpans(0, text.length, UserMentionSpan::class.java)
|
?.getSpans(0, text.length, MatrixItemSpan::class.java)
|
||||||
?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
|
?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
|
||||||
?.toMutableList()
|
?.toMutableList()
|
||||||
?.takeIf { it.isNotEmpty() }
|
?.takeIf { it.isNotEmpty() }
|
||||||
@ -65,7 +63,7 @@ internal class TextPillsUtils @Inject constructor(
|
|||||||
// append text before pill
|
// append text before pill
|
||||||
append(text, currIndex, start)
|
append(text, currIndex, start)
|
||||||
// append the pill
|
// append the pill
|
||||||
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.displayName))
|
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.getBestName()))
|
||||||
currIndex = end
|
currIndex = end
|
||||||
}
|
}
|
||||||
// append text after the last pill
|
// append text after the last pill
|
||||||
|
@ -21,7 +21,6 @@ import com.zhuinden.monarchy.Monarchy
|
|||||||
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
@ -38,7 +37,7 @@ internal class DefaultClearUnlinkedEventsTask @Inject constructor(private val mo
|
|||||||
monarchy.awaitTransaction { localRealm ->
|
monarchy.awaitTransaction { localRealm ->
|
||||||
val unlinkedChunks = ChunkEntity
|
val unlinkedChunks = ChunkEntity
|
||||||
.where(localRealm, roomId = params.roomId)
|
.where(localRealm, roomId = params.roomId)
|
||||||
.equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
|
.equalTo(ChunkEntityFields.IS_UNLINKED, true)
|
||||||
.findAll()
|
.findAll()
|
||||||
unlinkedChunks.forEach {
|
unlinkedChunks.forEach {
|
||||||
it.deleteOnCascade()
|
it.deleteOnCascade()
|
||||||
|
@ -27,13 +27,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
|||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.query.FilterContent
|
import im.vector.matrix.android.internal.database.query.FilterContent
|
||||||
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -44,16 +38,10 @@ import im.vector.matrix.android.internal.task.configureWith
|
|||||||
import im.vector.matrix.android.internal.util.Debouncer
|
import im.vector.matrix.android.internal.util.Debouncer
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import im.vector.matrix.android.internal.util.createUIHandler
|
import im.vector.matrix.android.internal.util.createUIHandler
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.*
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import io.realm.Sort
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Collections
|
import java.util.*
|
||||||
import java.util.UUID
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@ -77,11 +65,11 @@ internal class DefaultTimeline(
|
|||||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts
|
private val hiddenReadReceipts: TimelineHiddenReadReceipts
|
||||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||||
|
|
||||||
private companion object {
|
companion object {
|
||||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listeners = ArrayList<Timeline.Listener>()
|
private val listeners = CopyOnWriteArrayList<Timeline.Listener>()
|
||||||
private val isStarted = AtomicBoolean(false)
|
private val isStarted = AtomicBoolean(false)
|
||||||
private val isReady = AtomicBoolean(false)
|
private val isReady = AtomicBoolean(false)
|
||||||
private val mainHandler = createUIHandler()
|
private val mainHandler = createUIHandler()
|
||||||
@ -113,11 +101,7 @@ internal class DefaultTimeline(
|
|||||||
if (!results.isLoaded || !results.isValid) {
|
if (!results.isLoaded || !results.isValid) {
|
||||||
return@OrderedRealmCollectionChangeListener
|
return@OrderedRealmCollectionChangeListener
|
||||||
}
|
}
|
||||||
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
|
handleUpdates(changeSet)
|
||||||
handleInitialLoad()
|
|
||||||
} else {
|
|
||||||
handleUpdates(changeSet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
|
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
|
||||||
@ -179,8 +163,9 @@ internal class DefaultTimeline(
|
|||||||
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||||
filteredEvents = nonFilteredEvents.where()
|
filteredEvents = nonFilteredEvents.where()
|
||||||
.filterEventsWithSettings()
|
.filterEventsWithSettings()
|
||||||
.findAllAsync()
|
.findAll()
|
||||||
.also { it.addChangeListener(eventsChangeListener) }
|
handleInitialLoad()
|
||||||
|
filteredEvents.addChangeListener(eventsChangeListener)
|
||||||
|
|
||||||
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
@ -288,20 +273,20 @@ internal class DefaultTimeline(
|
|||||||
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Timeline.Listener) = synchronized(listeners) {
|
override fun addListener(listener: Timeline.Listener): Boolean {
|
||||||
if (listeners.contains(listener)) {
|
if (listeners.contains(listener)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
listeners.add(listener).also {
|
return listeners.add(listener).also {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeListener(listener: Timeline.Listener) = synchronized(listeners) {
|
override fun removeListener(listener: Timeline.Listener): Boolean {
|
||||||
listeners.remove(listener)
|
return listeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeAllListeners() = synchronized(listeners) {
|
override fun removeAllListeners() {
|
||||||
listeners.clear()
|
listeners.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,14 +387,14 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
private fun getState(direction: Timeline.Direction): State {
|
private fun getState(direction: Timeline.Direction): State {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState.get()
|
Timeline.Direction.FORWARDS -> forwardsState.get()
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
|
private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
|
||||||
val stateReference = when (direction) {
|
val stateReference = when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState
|
Timeline.Direction.FORWARDS -> forwardsState
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState
|
Timeline.Direction.BACKWARDS -> backwardsState
|
||||||
}
|
}
|
||||||
val currentValue = stateReference.get()
|
val currentValue = stateReference.get()
|
||||||
@ -504,15 +489,14 @@ internal class DefaultTimeline(
|
|||||||
Timber.v("Should fetch $limit items $direction")
|
Timber.v("Should fetch $limit items $direction")
|
||||||
cancelableBag += paginationTask
|
cancelableBag += paginationTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.retryCount = Int.MAX_VALUE
|
|
||||||
this.constraints = TaskConstraints(connectedToNetwork = true)
|
this.constraints = TaskConstraints(connectedToNetwork = true)
|
||||||
this.callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
this.callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||||
when (data) {
|
when (data) {
|
||||||
TokenChunkEventPersistor.Result.SUCCESS -> {
|
TokenChunkEventPersistor.Result.SUCCESS -> {
|
||||||
Timber.v("Success fetching $limit items $direction from pagination request")
|
Timber.v("Success fetching $limit items $direction from pagination request")
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
||||||
@ -524,6 +508,8 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
|
postSnapshot()
|
||||||
Timber.v("Failure fetching $limit items $direction from pagination request")
|
Timber.v("Failure fetching $limit items $direction from pagination request")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -637,7 +623,14 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
private fun fetchEvent(eventId: String) {
|
private fun fetchEvent(eventId: String) {
|
||||||
val params = GetContextOfEventTask.Params(roomId, eventId, settings.initialSize)
|
val params = GetContextOfEventTask.Params(roomId, eventId, settings.initialSize)
|
||||||
cancelableBag += contextOfEventTask.configureWith(params).executeBy(taskExecutor)
|
cancelableBag += contextOfEventTask.configureWith(params) {
|
||||||
|
callback = object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
postFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postSnapshot() {
|
private fun postSnapshot() {
|
||||||
@ -648,16 +641,26 @@ internal class DefaultTimeline(
|
|||||||
updateLoadingStates(filteredEvents)
|
updateLoadingStates(filteredEvents)
|
||||||
val snapshot = createSnapshot()
|
val snapshot = createSnapshot()
|
||||||
val runnable = Runnable {
|
val runnable = Runnable {
|
||||||
synchronized(listeners) {
|
listeners.forEach {
|
||||||
listeners.forEach {
|
it.onTimelineUpdated(snapshot)
|
||||||
it.onUpdated(snapshot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debouncer.debounce("post_snapshot", runnable, 50)
|
debouncer.debounce("post_snapshot", runnable, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun postFailure(throwable: Throwable) {
|
||||||
|
if (isReady.get().not()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val runnable = Runnable {
|
||||||
|
listeners.forEach {
|
||||||
|
it.onTimelineFailure(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainHandler.post(runnable)
|
||||||
|
}
|
||||||
|
|
||||||
private fun clearAllValues() {
|
private fun clearAllValues() {
|
||||||
prevDisplayIndex = null
|
prevDisplayIndex = null
|
||||||
nextDisplayIndex = null
|
nextDisplayIndex = null
|
||||||
|
@ -24,7 +24,6 @@ import im.vector.matrix.android.internal.database.query.create
|
|||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -33,7 +32,8 @@ import javax.inject.Inject
|
|||||||
/**
|
/**
|
||||||
* Insert Chunk in DB, and eventually merge with existing chunk event
|
* Insert Chunk in DB, and eventually merge with existing chunk event
|
||||||
*/
|
*/
|
||||||
internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy) {
|
internal class TokenChunkEventPersistor @Inject constructor(private val monarchy: Monarchy,
|
||||||
|
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -112,7 +112,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
val nextToken: String?
|
val nextToken: String?
|
||||||
val prevToken: String?
|
val prevToken: String?
|
||||||
@ -125,34 +125,29 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
}
|
}
|
||||||
|
|
||||||
val shouldSkip = ChunkEntity.find(realm, roomId, nextToken = nextToken) != null
|
val shouldSkip = ChunkEntity.find(realm, roomId, nextToken = nextToken) != null
|
||||||
|| ChunkEntity.find(realm, roomId, prevToken = prevToken) != null
|
|| ChunkEntity.find(realm, roomId, prevToken = prevToken) != null
|
||||||
|
|
||||||
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
||||||
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
||||||
|
|
||||||
// The current chunk is the one we will keep all along the merge processChanges.
|
// The current chunk is the one we will keep all along the merge processChanges.
|
||||||
// We try to look for a chunk next to the token,
|
// We try to look for a chunk next to the token,
|
||||||
// otherwise we create a whole new one
|
// otherwise we create a whole new one which is unlinked (not live)
|
||||||
|
|
||||||
var currentChunk = if (direction == PaginationDirection.FORWARDS) {
|
var currentChunk = if (direction == PaginationDirection.FORWARDS) {
|
||||||
prevChunk?.apply { this.nextToken = nextToken }
|
prevChunk?.apply { this.nextToken = nextToken }
|
||||||
} else {
|
} else {
|
||||||
nextChunk?.apply { this.prevToken = prevToken }
|
nextChunk?.apply { this.prevToken = prevToken }
|
||||||
}
|
}
|
||||||
?: ChunkEntity.create(realm, prevToken, nextToken)
|
?: ChunkEntity.create(realm, prevToken, nextToken, isUnlinked = true)
|
||||||
|
|
||||||
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
|
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
|
||||||
Timber.v("Reach end of $roomId")
|
Timber.v("Reach end of $roomId")
|
||||||
currentChunk.isLastBackward = true
|
currentChunk.isLastBackward = true
|
||||||
} else if (!shouldSkip) {
|
} else if (!shouldSkip) {
|
||||||
Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
|
Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}")
|
||||||
val eventIds = ArrayList<String>(receivedChunk.events.size)
|
val timelineEvents = receivedChunk.events.mapNotNull {
|
||||||
for (event in receivedChunk.events) {
|
currentChunk.add(roomId, it, direction)
|
||||||
event.eventId?.also { eventIds.add(it) }
|
|
||||||
currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked())
|
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
|
||||||
realm.insertOrUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Then we merge chunks if needed
|
// Then we merge chunks if needed
|
||||||
if (currentChunk != prevChunk && prevChunk != null) {
|
if (currentChunk != prevChunk && prevChunk != null) {
|
||||||
@ -170,12 +165,9 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
}
|
}
|
||||||
roomEntity.addOrUpdate(currentChunk)
|
roomEntity.addOrUpdate(currentChunk)
|
||||||
for (stateEvent in receivedChunk.stateEvents) {
|
for (stateEvent in receivedChunk.stateEvents) {
|
||||||
roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked())
|
roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked)
|
||||||
UserEntityFactory.createOrNull(stateEvent)?.also {
|
|
||||||
realm.insertOrUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
currentChunk.updateSenderDataFor(eventIds)
|
timelineEventSenderVisitor.visit(timelineEvents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (receivedChunk.events.isEmpty()) {
|
return if (receivedChunk.events.isEmpty()) {
|
||||||
@ -196,11 +188,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
// We always merge the bottom chunk into top chunk, so we are always merging backwards
|
// We always merge the bottom chunk into top chunk, so we are always merging backwards
|
||||||
Timber.v("Merge ${currentChunk.prevToken} | ${currentChunk.nextToken} with ${otherChunk.prevToken} | ${otherChunk.nextToken}")
|
Timber.v("Merge ${currentChunk.prevToken} | ${currentChunk.nextToken} with ${otherChunk.prevToken} | ${otherChunk.nextToken}")
|
||||||
return if (direction == PaginationDirection.BACKWARDS && !otherChunk.isLastForward) {
|
return if (direction == PaginationDirection.BACKWARDS && !otherChunk.isLastForward) {
|
||||||
currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
|
val events = currentChunk.merge(roomEntity.roomId, otherChunk, PaginationDirection.BACKWARDS)
|
||||||
|
timelineEventSenderVisitor.visit(events)
|
||||||
roomEntity.deleteOnCascade(otherChunk)
|
roomEntity.deleteOnCascade(otherChunk)
|
||||||
currentChunk
|
currentChunk
|
||||||
} else {
|
} else {
|
||||||
otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
|
val events = otherChunk.merge(roomEntity.roomId, currentChunk, PaginationDirection.BACKWARDS)
|
||||||
|
timelineEventSenderVisitor.visit(events)
|
||||||
roomEntity.deleteOnCascade(currentChunk)
|
roomEntity.deleteOnCascade(currentChunk)
|
||||||
otherChunk
|
otherChunk
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
@ -30,9 +31,9 @@ import im.vector.matrix.android.internal.database.query.types
|
|||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDatabase
|
internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDatabase
|
||||||
@ -51,24 +52,24 @@ internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDataba
|
|||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
.also {
|
.also {
|
||||||
handleRoomTombstoneEvents(it)
|
observerScope.launch {
|
||||||
|
handleRoomTombstoneEvents(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRoomTombstoneEvents(tombstoneEvents: List<Event>) = Realm.getInstance(realmConfiguration).use {
|
private suspend fun handleRoomTombstoneEvents(tombstoneEvents: List<Event>) = awaitTransaction(realmConfiguration) { realm ->
|
||||||
it.executeTransactionAsync { realm ->
|
for (event in tombstoneEvents) {
|
||||||
for (event in tombstoneEvents) {
|
if (event.roomId == null) continue
|
||||||
if (event.roomId == null) continue
|
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
|
||||||
val createRoomContent = event.getClearContent().toModel<RoomTombstoneContent>()
|
if (createRoomContent?.replacementRoom == null) continue
|
||||||
if (createRoomContent?.replacementRoom == null) continue
|
|
||||||
|
|
||||||
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst()
|
val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst()
|
||||||
?: RoomSummaryEntity(event.roomId)
|
?: RoomSummaryEntity(event.roomId)
|
||||||
if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
|
if (predecessorRoomSummary.versioningState == VersioningState.NONE) {
|
||||||
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED
|
predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED
|
||||||
}
|
|
||||||
realm.insertOrUpdate(predecessorRoomSummary)
|
|
||||||
}
|
}
|
||||||
|
realm.insertOrUpdate(predecessorRoomSummary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user