mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +03:00
Merge pull request #792 from vector-im/feature/stabilization
Feature/stabilization
This commit is contained in:
commit
898bf234da
103 changed files with 1447 additions and 1091 deletions
|
@ -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,7 +19,10 @@ 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.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
@ -29,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
|
||||||
|
@ -146,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 ->
|
||||||
|
@ -177,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
|
||||||
}
|
}
|
||||||
|
@ -191,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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 @@ 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
|
* This methods return true if an initial sync has been processed
|
||||||
|
|
|
@ -38,9 +38,15 @@ interface GroupService {
|
||||||
*/
|
*/
|
||||||
fun getGroupSummary(groupId: String): GroupSummary?
|
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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,16 +60,28 @@ interface RoomService {
|
||||||
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
fun getRoomSummary(roomIdOrAlias: String): RoomSummary?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a snapshot list of room summaries.
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @return the immutable list of [RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun liveRoomSummaries(): LiveData<List<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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
|
}
|
|
@ -41,7 +41,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
|
||||||
|
|
|
@ -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>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -146,3 +147,4 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
|
||||||
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 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)
|
||||||
|
|
|
@ -59,6 +59,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(private val
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
val realmConfiguration = RealmConfiguration.Builder()
|
||||||
|
.compactOnLaunch()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.name(REALM_NAME)
|
.name(REALM_NAME)
|
||||||
.apply {
|
.apply {
|
||||||
|
|
|
@ -28,15 +28,7 @@ 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()
|
||||||
|
@ -46,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 }
|
||||||
|
@ -65,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) {
|
||||||
|
@ -129,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
|
||||||
|
|
||||||
|
@ -151,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()
|
||||||
|
@ -165,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)
|
||||||
|
}
|
|
@ -70,7 +70,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,12 +14,20 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.composer
|
package im.vector.matrix.android.internal.query
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
|
||||||
sealed class TextComposerAction : VectorViewModelAction {
|
fun <T : RealmObject, E : Enum<E>> RealmQuery<T>.process(field: String, enums: List<Enum<E>>): RealmQuery<T> {
|
||||||
data class QueryUsers(val query: CharSequence?) : TextComposerAction()
|
val lastEnumValue = enums.lastOrNull()
|
||||||
data class QueryRooms(val query: CharSequence?) : TextComposerAction()
|
beginGroup()
|
||||||
data class QueryGroups(val query: CharSequence?) : TextComposerAction()
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -156,7 +156,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
syncTaskSequencer.close()
|
syncTaskSequencer.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun syncState(): LiveData<SyncState> {
|
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||||
return getSyncThread().liveState()
|
return getSyncThread().liveState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +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 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 {
|
||||||
|
@ -41,10 +45,23 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveGroupSummaries(): LiveData<List<GroupSummary>> {
|
override fun getGroupSummaries(groupSummaryQueryParams: GroupSummaryQueryParams): List<GroupSummary> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) },
|
{ 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -32,6 +33,7 @@ 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.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
|
||||||
|
@ -41,6 +43,7 @@ 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 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,
|
||||||
|
@ -86,30 +89,51 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ realm ->
|
{ roomSummariesQuery(it, queryParams) },
|
||||||
RoomSummaryEntity.where(realm)
|
|
||||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
|
||||||
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
|
||||||
},
|
|
||||||
{ 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))
|
||||||
|
|
|
@ -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>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ 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.*
|
||||||
|
@ -93,10 +93,11 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
||||||
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_ROOM_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)
|
||||||
|
@ -105,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)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +97,10 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -508,10 +493,10 @@ internal class DefaultTimeline(
|
||||||
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 ->
|
||||||
|
@ -656,10 +641,8 @@ 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.onTimelineUpdated(snapshot)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debouncer.debounce("post_snapshot", runnable, 50)
|
debouncer.debounce("post_snapshot", runnable, 50)
|
||||||
|
@ -671,10 +654,8 @@ internal class DefaultTimeline(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val runnable = Runnable {
|
val runnable = Runnable {
|
||||||
synchronized(listeners) {
|
listeners.forEach {
|
||||||
listeners.forEach {
|
it.onTimelineFailure(throwable)
|
||||||
it.onTimelineFailure(throwable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mainHandler.post(runnable)
|
mainHandler.post(runnable)
|
||||||
|
|
|
@ -18,17 +18,12 @@ package im.vector.matrix.android.internal.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.internal.database.helper.*
|
||||||
import im.vector.matrix.android.internal.database.helper.add
|
|
||||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
|
||||||
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
|
||||||
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.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.query.create
|
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
|
||||||
|
@ -37,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>
|
||||||
|
@ -136,27 +132,22 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -174,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()) {
|
||||||
|
@ -200,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,17 @@ import im.vector.matrix.android.internal.database.helper.*
|
||||||
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.EventEntityFields
|
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.TimelineEventEntity
|
||||||
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.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
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.DefaultInitialSyncProgressService
|
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
||||||
import im.vector.matrix.android.internal.session.mapWithProgress
|
import im.vector.matrix.android.internal.session.mapWithProgress
|
||||||
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.room.membership.RoomMemberEventHandler
|
||||||
import im.vector.matrix.android.internal.session.room.read.FullyReadContent
|
import im.vector.matrix.android.internal.session.room.read.FullyReadContent
|
||||||
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.internal.session.sync.model.*
|
import im.vector.matrix.android.internal.session.sync.model.*
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -46,7 +47,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||||
private val roomTagHandler: RoomTagHandler,
|
private val roomTagHandler: RoomTagHandler,
|
||||||
private val roomFullyReadHandler: RoomFullyReadHandler,
|
private val roomFullyReadHandler: RoomFullyReadHandler,
|
||||||
private val cryptoService: DefaultCryptoService) {
|
private val cryptoService: DefaultCryptoService,
|
||||||
|
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||||
|
private val timelineEventSenderVisitor: TimelineEventSenderVisitor) {
|
||||||
|
|
||||||
sealed class HandlingStrategy {
|
sealed class HandlingStrategy {
|
||||||
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
|
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||||
|
@ -119,9 +122,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoService.onStateEvent(roomId, event)
|
cryptoService.onStateEvent(roomId, event)
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
roomMemberEventHandler.handle(realm, roomId, event)
|
||||||
realm.insertOrUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
|
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
|
||||||
|
@ -189,11 +190,13 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
}
|
}
|
||||||
lastChunk?.isLastForward = false
|
lastChunk?.isLastForward = false
|
||||||
chunkEntity.isLastForward = true
|
chunkEntity.isLastForward = true
|
||||||
|
chunkEntity.isUnlinked = false
|
||||||
|
|
||||||
val eventIds = ArrayList<String>(eventList.size)
|
val timelineEvents = ArrayList<TimelineEventEntity>(eventList.size)
|
||||||
for (event in eventList) {
|
for (event in eventList) {
|
||||||
event.eventId?.also { eventIds.add(it) }
|
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)?.also {
|
||||||
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
|
timelineEvents.add(it)
|
||||||
|
}
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoService.onLiveEvent(roomEntity.roomId, event)
|
cryptoService.onLiveEvent(roomEntity.roomId, event)
|
||||||
// Try to remove local echo
|
// Try to remove local echo
|
||||||
|
@ -206,11 +209,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
Timber.v("Can't find corresponding local echo for tx:$it")
|
Timber.v("Can't find corresponding local echo for tx:$it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
|
||||||
realm.insertOrUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
chunkEntity.updateSenderDataFor(eventIds)
|
timelineEventSenderVisitor.visit(timelineEvents)
|
||||||
return chunkEntity
|
return chunkEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.pushrules.RuleScope
|
import im.vector.matrix.android.api.pushrules.RuleScope
|
||||||
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
||||||
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.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
|
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
|
||||||
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.*
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
|
@ -69,9 +69,9 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
|
||||||
var hasUpdate = false
|
var hasUpdate = false
|
||||||
monarchy.doWithRealm { realm ->
|
monarchy.doWithRealm { realm ->
|
||||||
invites.forEach { (roomId, _) ->
|
invites.forEach { (roomId, _) ->
|
||||||
val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(userId)
|
val myUserStateEvent = RoomMembers(realm, roomId).getLastStateEvent(userId)
|
||||||
val inviterId = myUserStateEvent?.sender
|
val inviterId = myUserStateEvent?.sender
|
||||||
val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
|
val myUserRoomMember: RoomMemberContent? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
|
||||||
val isDirect = myUserRoomMember?.isDirect
|
val isDirect = myUserRoomMember?.isDirect
|
||||||
if (inviterId != null && inviterId != userId && isDirect == true) {
|
if (inviterId != null && inviterId != userId && isDirect == true) {
|
||||||
directChats
|
directChats
|
||||||
|
|
|
@ -112,7 +112,7 @@ abstract class SyncService : Service() {
|
||||||
try {
|
try {
|
||||||
syncTask.execute(params)
|
syncTask.execute(params)
|
||||||
// Start sync if we were doing an initial sync and the syncThread is not launched yet
|
// Start sync if we were doing an initial sync and the syncThread is not launched yet
|
||||||
if (isInitialSync && session.syncState().value == SyncState.Idle) {
|
if (isInitialSync && session.getSyncStateLive().value == SyncState.Idle) {
|
||||||
val isForeground = !backgroundDetectionObserver.isInBackground
|
val isForeground = !backgroundDetectionObserver.isInBackground
|
||||||
session.startSync(isForeground)
|
session.startSync(isForeground)
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
return userEntity.asDomain()
|
return userEntity.asDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveUser(userId: String): LiveData<Optional<User>> {
|
override fun getUserLive(userId: String): LiveData<Optional<User>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
{ UserEntity.where(it, userId) },
|
{ UserEntity.where(it, userId) },
|
||||||
{ it.asDomain() }
|
{ it.asDomain() }
|
||||||
|
@ -80,7 +80,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveUsers(): LiveData<List<User>> {
|
override fun getUsersLive(): LiveData<List<User>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm ->
|
{ realm ->
|
||||||
realm.where(UserEntity::class.java)
|
realm.where(UserEntity::class.java)
|
||||||
|
@ -91,7 +91,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun livePagedUsers(filter: String?): LiveData<PagedList<User>> {
|
override fun getPagedUsersLive(filter: String?): LiveData<PagedList<User>> {
|
||||||
realmDataSourceFactory.updateQuery { realm ->
|
realmDataSourceFactory.updateQuery { realm ->
|
||||||
val query = realm.where(UserEntity::class.java)
|
val query = realm.where(UserEntity::class.java)
|
||||||
if (filter.isNullOrEmpty()) {
|
if (filter.isNullOrEmpty()) {
|
||||||
|
@ -121,7 +121,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveIgnoredUsers(): LiveData<List<User>> {
|
override fun getIgnoredUsersLive(): LiveData<List<User>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm ->
|
{ realm ->
|
||||||
realm.where(IgnoredUserEntity::class.java)
|
realm.where(IgnoredUserEntity::class.java)
|
||||||
|
|
|
@ -16,27 +16,16 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.user
|
package im.vector.matrix.android.internal.session.user
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
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.RoomMember
|
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||||
|
|
||||||
internal object UserEntityFactory {
|
internal object UserEntityFactory {
|
||||||
|
|
||||||
fun createOrNull(event: Event): UserEntity? {
|
fun create(userId: String, roomMember: RoomMemberContent): UserEntity {
|
||||||
if (event.type != EventType.STATE_ROOM_MEMBER) {
|
return UserEntity(
|
||||||
return null
|
userId = userId,
|
||||||
}
|
displayName = roomMember.displayName ?: "",
|
||||||
val roomMember = event.content.toModel<RoomMember>() ?: return null
|
avatarUrl = roomMember.avatarUrl ?: ""
|
||||||
// We only use JOIN and INVITED memberships to create User data
|
|
||||||
if (roomMember.membership != Membership.JOIN && roomMember.membership != Membership.INVITE) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return UserEntity(event.stateKey ?: "",
|
|
||||||
roomMember.displayName ?: "",
|
|
||||||
roomMember.avatarUrl ?: ""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
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.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.RoomMemberContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -40,7 +40,7 @@ class PushrulesConditionTest {
|
||||||
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
|
content = MessageTextContent("m.text", "Yo wtf?").toContent(),
|
||||||
originServerTs = 0)
|
originServerTs = 0)
|
||||||
|
|
||||||
val rm = RoomMember(
|
val rm = RoomMemberContent(
|
||||||
Membership.INVITE,
|
Membership.INVITE,
|
||||||
displayName = "Foo",
|
displayName = "Foo",
|
||||||
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
|
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
|
||||||
|
@ -72,7 +72,7 @@ class PushrulesConditionTest {
|
||||||
type = "m.room.member",
|
type = "m.room.member",
|
||||||
eventId = "mx0",
|
eventId = "mx0",
|
||||||
stateKey = "@foo:matrix.org",
|
stateKey = "@foo:matrix.org",
|
||||||
content = RoomMember(
|
content = RoomMemberContent(
|
||||||
Membership.INVITE,
|
Membership.INVITE,
|
||||||
displayName = "Foo",
|
displayName = "Foo",
|
||||||
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
|
avatarUrl = "mxc://matrix.org/EqMZYbREvHXvYFyfxOlkf"
|
||||||
|
|
|
@ -218,8 +218,8 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
def epoxy_version = '3.8.0'
|
def epoxy_version = '3.9.0'
|
||||||
def fragment_version = '1.2.0-rc01'
|
def fragment_version = '1.2.0-rc04'
|
||||||
def arrow_version = "0.8.2"
|
def arrow_version = "0.8.2"
|
||||||
def coroutines_version = "1.3.2"
|
def coroutines_version = "1.3.2"
|
||||||
def markwon_version = '4.1.2'
|
def markwon_version = '4.1.2'
|
||||||
|
@ -227,7 +227,7 @@ dependencies {
|
||||||
def glide_version = '4.10.0'
|
def glide_version = '4.10.0'
|
||||||
def moshi_version = '1.8.0'
|
def moshi_version = '1.8.0'
|
||||||
def daggerVersion = '2.24'
|
def daggerVersion = '2.24'
|
||||||
def autofill_version = "1.0.0-rc01"
|
def autofill_version = "1.0.0"
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
|
@ -238,11 +238,11 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation "androidx.fragment:fragment:$fragment_version"
|
implementation "androidx.fragment:fragment:$fragment_version"
|
||||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||||
//Do not use beta2 at the moment, as it breaks things
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
|
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
|
|
||||||
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
|
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
|
||||||
|
@ -275,10 +275,10 @@ dependencies {
|
||||||
implementation 'com.airbnb.android:mvrx:1.3.0'
|
implementation 'com.airbnb.android:mvrx:1.3.0'
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"
|
implementation "androidx.work:work-runtime-ktx:2.3.0-beta02"
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||||
|
|
||||||
// Functional Programming
|
// Functional Programming
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
@ -288,7 +288,7 @@ dependencies {
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.2.0-alpha03'
|
||||||
implementation 'me.gujun.android:span:1.7'
|
implementation 'me.gujun.android:span:1.7'
|
||||||
implementation "io.noties.markwon:core:$markwon_version"
|
implementation "io.noties.markwon:core:$markwon_version"
|
||||||
implementation "io.noties.markwon:html:$markwon_version"
|
implementation "io.noties.markwon:html:$markwon_version"
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.lifecycle.OnLifecycleEvent
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
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.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.features.home.HomeRoomListDataSource
|
import im.vector.riotx.features.home.HomeRoomListDataSource
|
||||||
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
|
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
|
||||||
|
@ -65,7 +66,8 @@ class AppStateHandler @Inject constructor(
|
||||||
sessionDataSource.observe()
|
sessionDataSource.observe()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.switchMap {
|
.switchMap {
|
||||||
it.orNull()?.rx()?.liveRoomSummaries()
|
val query = roomSummaryQueryParams {}
|
||||||
|
it.orNull()?.rx()?.liveRoomSummaries(query)
|
||||||
?: Observable.just(emptyList())
|
?: Observable.just(emptyList())
|
||||||
}
|
}
|
||||||
.throttleLast(300, TimeUnit.MILLISECONDS),
|
.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||||
|
|
|
@ -63,7 +63,8 @@ import im.vector.riotx.features.ui.UiStateRepository
|
||||||
ViewModelModule::class,
|
ViewModelModule::class,
|
||||||
FragmentModule::class,
|
FragmentModule::class,
|
||||||
HomeModule::class,
|
HomeModule::class,
|
||||||
RoomListModule::class
|
RoomListModule::class,
|
||||||
|
ScreenModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ScreenScope
|
@ScreenScope
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.riotx.core.di
|
package im.vector.riotx.core.di
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
|
@ -27,4 +28,9 @@ object ScreenModule {
|
||||||
@Provides
|
@Provides
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun providesGlideRequests(context: AppCompatActivity) = GlideApp.with(context)
|
fun providesGlideRequests(context: AppCompatActivity) = GlideApp.with(context)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@ScreenScope
|
||||||
|
fun providesSharedViewPool() = RecyclerView.RecycledViewPool()
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ import im.vector.riotx.features.notifications.*
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.VectorFileLogger
|
import im.vector.riotx.features.rageshake.VectorFileLogger
|
||||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||||
|
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.share.ShareRoomListDataSource
|
import im.vector.riotx.features.share.ShareRoomListDataSource
|
||||||
|
@ -124,6 +125,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun uiStateRepository(): UiStateRepository
|
fun uiStateRepository(): UiStateRepository
|
||||||
|
|
||||||
|
fun emojiDataSource(): EmojiDataSource
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance context: Context): VectorComponent
|
fun create(@BindsInstance context: Context): VectorComponent
|
||||||
|
|
|
@ -18,14 +18,25 @@ package im.vector.riotx.core.epoxy
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EpoxyModelWithHolder which can listen to visibility state change
|
* EpoxyModelWithHolder which can listen to visibility state change
|
||||||
*/
|
*/
|
||||||
abstract class VectorEpoxyModel<H : VectorEpoxyHolder> : EpoxyModelWithHolder<H>() {
|
abstract class VectorEpoxyModel<H : VectorEpoxyHolder> : EpoxyModelWithHolder<H>() {
|
||||||
|
|
||||||
|
protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null
|
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null
|
||||||
|
|
||||||
|
override fun unbind(holder: H) {
|
||||||
|
coroutineScope.coroutineContext.cancelChildren()
|
||||||
|
super.unbind(holder)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onVisibilityStateChanged(visibilityState: Int, view: H) {
|
override fun onVisibilityStateChanged(visibilityState: Int, view: H) {
|
||||||
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(visibilityState)
|
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(visibilityState)
|
||||||
super.onVisibilityStateChanged(visibilityState, view)
|
super.onVisibilityStateChanged(visibilityState, view)
|
||||||
|
|
|
@ -51,7 +51,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
holder.sender.setTextOrHide(matrixItem.displayName)
|
holder.sender.setTextOrHide(matrixItem.displayName)
|
||||||
holder.body.movementMethod = movementMethod
|
holder.body.movementMethod = movementMethod
|
||||||
holder.body.text = body
|
holder.body.text = body
|
||||||
body.findPillsAndProcess { it.bind(holder.body) }
|
body.findPillsAndProcess(coroutineScope) { it.bind(holder.body) }
|
||||||
holder.timestamp.setTextOrHide(time)
|
holder.timestamp.setTextOrHide(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,13 @@ import com.airbnb.epoxy.EpoxyController
|
||||||
*/
|
*/
|
||||||
fun RecyclerView.configureWith(epoxyController: EpoxyController,
|
fun RecyclerView.configureWith(epoxyController: EpoxyController,
|
||||||
itemAnimator: RecyclerView.ItemAnimator? = null,
|
itemAnimator: RecyclerView.ItemAnimator? = null,
|
||||||
|
viewPool: RecyclerView.RecycledViewPool? = null,
|
||||||
showDivider: Boolean = false,
|
showDivider: Boolean = false,
|
||||||
hasFixedSize: Boolean = true) {
|
hasFixedSize: Boolean = true) {
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply {
|
||||||
|
recycleChildrenOnDetach = viewPool != null
|
||||||
|
}
|
||||||
|
setRecycledViewPool(viewPool)
|
||||||
itemAnimator?.let { this.itemAnimator = it }
|
itemAnimator?.let { this.itemAnimator = it }
|
||||||
if (showDivider) {
|
if (showDivider) {
|
||||||
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
|
|
|
@ -45,15 +45,13 @@ class VectorSyncService : SyncService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart(isInitialSync: Boolean) {
|
override fun onStart(isInitialSync: Boolean) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val notificationSubtitleRes = if (isInitialSync) {
|
||||||
val notificationSubtitleRes = if (isInitialSync) {
|
R.string.notification_initial_sync
|
||||||
R.string.notification_initial_sync
|
} else {
|
||||||
} else {
|
R.string.notification_listening_for_events
|
||||||
R.string.notification_listening_for_events
|
|
||||||
}
|
|
||||||
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
|
||||||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
|
||||||
}
|
}
|
||||||
|
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
|
||||||
|
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) {
|
override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) {
|
||||||
|
|
|
@ -37,10 +37,6 @@ class AutocompleteEmojiController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
fontProvider.addListener(fontProviderListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
var listener: AutocompleteClickListener<String>? = null
|
var listener: AutocompleteClickListener<String>? = null
|
||||||
|
|
||||||
override fun buildModels(data: List<EmojiItem>?) {
|
override fun buildModels(data: List<EmojiItem>?) {
|
||||||
|
@ -71,6 +67,10 @@ class AutocompleteEmojiController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
fontProvider.addListener(fontProviderListener)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
super.onDetachedFromRecyclerView(recyclerView)
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
fontProvider.removeListener(fontProviderListener)
|
fontProvider.removeListener(fontProviderListener)
|
||||||
|
|
|
@ -18,19 +18,19 @@ package im.vector.riotx.features.autocomplete.group
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
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.riotx.features.autocomplete.AutocompleteClickListener
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteGroupPresenter @Inject constructor(context: Context,
|
class AutocompleteGroupPresenter @Inject constructor(context: Context,
|
||||||
private val controller: AutocompleteGroupController
|
private val controller: AutocompleteGroupController,
|
||||||
|
private val session: Session
|
||||||
) : RecyclerViewPresenter<GroupSummary>(context), AutocompleteClickListener<GroupSummary> {
|
) : RecyclerViewPresenter<GroupSummary>(context), AutocompleteClickListener<GroupSummary> {
|
||||||
|
|
||||||
var callback: Callback? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,16 @@ class AutocompleteGroupPresenter @Inject constructor(context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
override fun onQuery(query: CharSequence?) {
|
||||||
callback?.onQueryGroups(query)
|
val queryParams = groupSummaryQueryParams {
|
||||||
}
|
displayName = if (query.isNullOrBlank()) {
|
||||||
|
QueryStringValue.IsNotEmpty
|
||||||
fun render(groups: Async<List<GroupSummary>>) {
|
} else {
|
||||||
if (groups is Success) {
|
QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
|
||||||
controller.setData(groups())
|
}
|
||||||
}
|
}
|
||||||
}
|
val groups = session.getGroupSummaries(queryParams)
|
||||||
|
.asSequence()
|
||||||
interface Callback {
|
.sortedBy { it.displayName }
|
||||||
fun onQueryGroups(query: CharSequence?)
|
controller.setData(groups.toList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,23 +14,23 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.autocomplete.user
|
package im.vector.riotx.features.autocomplete.member
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteUserController @Inject constructor() : TypedEpoxyController<List<User>>() {
|
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMember>>() {
|
||||||
|
|
||||||
var listener: AutocompleteClickListener<User>? = null
|
var listener: AutocompleteClickListener<RoomMember>? = null
|
||||||
|
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
override fun buildModels(data: List<User>?) {
|
override fun buildModels(data: List<RoomMember>?) {
|
||||||
if (data.isNullOrEmpty()) {
|
if (data.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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.riotx.features.autocomplete.member
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
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.RoomMember
|
||||||
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
|
|
||||||
|
class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
|
||||||
|
@Assisted val roomId: String,
|
||||||
|
private val session: Session,
|
||||||
|
private val controller: AutocompleteMemberController
|
||||||
|
) : RecyclerViewPresenter<RoomMember>(context), AutocompleteClickListener<RoomMember> {
|
||||||
|
|
||||||
|
private val room = session.getRoom(roomId)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): AutocompleteMemberPresenter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
||||||
|
// Also remove animation
|
||||||
|
recyclerView?.itemAnimator = null
|
||||||
|
return controller.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(t: RoomMember) {
|
||||||
|
dispatchClick(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuery(query: CharSequence?) {
|
||||||
|
val queryParams = roomMemberQueryParams {
|
||||||
|
displayName = if (query.isNullOrBlank()) {
|
||||||
|
QueryStringValue.IsNotEmpty
|
||||||
|
} else {
|
||||||
|
QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
|
||||||
|
}
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
}
|
||||||
|
val members = room.getRoomMembers(queryParams)
|
||||||
|
.asSequence()
|
||||||
|
.sortedBy { it.displayName }
|
||||||
|
controller.setData(members.toList())
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,12 +24,10 @@ import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteRoomController @Inject constructor() : TypedEpoxyController<List<RoomSummary>>() {
|
class AutocompleteRoomController @Inject constructor(private val avatarRenderer: AvatarRenderer) : TypedEpoxyController<List<RoomSummary>>() {
|
||||||
|
|
||||||
var listener: AutocompleteClickListener<RoomSummary>? = null
|
var listener: AutocompleteClickListener<RoomSummary>? = null
|
||||||
|
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
|
||||||
|
|
||||||
override fun buildModels(data: List<RoomSummary>?) {
|
override fun buildModels(data: List<RoomSummary>?) {
|
||||||
if (data.isNullOrEmpty()) {
|
if (data.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -18,19 +18,19 @@ package im.vector.riotx.features.autocomplete.room
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
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.roomSummaryQueryParams
|
||||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteRoomPresenter @Inject constructor(context: Context,
|
class AutocompleteRoomPresenter @Inject constructor(context: Context,
|
||||||
private val controller: AutocompleteRoomController
|
private val controller: AutocompleteRoomController,
|
||||||
|
private val session: Session
|
||||||
) : RecyclerViewPresenter<RoomSummary>(context), AutocompleteClickListener<RoomSummary> {
|
) : RecyclerViewPresenter<RoomSummary>(context), AutocompleteClickListener<RoomSummary> {
|
||||||
|
|
||||||
var callback: Callback? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,16 @@ class AutocompleteRoomPresenter @Inject constructor(context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
override fun onQuery(query: CharSequence?) {
|
||||||
callback?.onQueryRooms(query)
|
val queryParams = roomSummaryQueryParams {
|
||||||
}
|
canonicalAlias = if (query.isNullOrBlank()) {
|
||||||
|
QueryStringValue.IsNotNull
|
||||||
fun render(rooms: Async<List<RoomSummary>>) {
|
} else {
|
||||||
if (rooms is Success) {
|
QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
|
||||||
controller.setData(rooms())
|
}
|
||||||
}
|
}
|
||||||
}
|
val rooms = session.getRoomSummaries(queryParams)
|
||||||
|
.asSequence()
|
||||||
interface Callback {
|
.sortedBy { it.displayName }
|
||||||
fun onQueryRooms(query: CharSequence?)
|
controller.setData(rooms.toList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +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.riotx.features.autocomplete.user
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.Success
|
|
||||||
import com.otaliastudios.autocomplete.RecyclerViewPresenter
|
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
|
||||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AutocompleteUserPresenter @Inject constructor(context: Context,
|
|
||||||
private val controller: AutocompleteUserController
|
|
||||||
) : RecyclerViewPresenter<User>(context), AutocompleteClickListener<User> {
|
|
||||||
|
|
||||||
var callback: Callback? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
controller.listener = this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
|
|
||||||
// Also remove animation
|
|
||||||
recyclerView?.itemAnimator = null
|
|
||||||
return controller.adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(t: User) {
|
|
||||||
dispatchClick(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onQuery(query: CharSequence?) {
|
|
||||||
callback?.onQueryUsers(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun render(users: Async<List<User>>) {
|
|
||||||
if (users is Success) {
|
|
||||||
controller.setData(users())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
fun onQueryUsers(query: CharSequence?)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -71,6 +71,14 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
.into(target)
|
.into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun getCachedDrawable(glideRequest: GlideRequests, matrixItem: MatrixItem): Drawable {
|
||||||
|
return buildGlideRequest(glideRequest, matrixItem.avatarUrl)
|
||||||
|
.onlyRetrieveFromCache(true)
|
||||||
|
.submit()
|
||||||
|
.get()
|
||||||
|
}
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
||||||
val avatarColor = when (matrixItem) {
|
val avatarColor = when (matrixItem) {
|
||||||
|
|
|
@ -66,6 +66,11 @@ class HomeDetailFragment @Inject constructor(
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupKeysBackupBanner()
|
setupKeysBackupBanner()
|
||||||
|
|
||||||
|
withState(viewModel) {
|
||||||
|
// Update the navigation view if needed (for when we restore the tabs)
|
||||||
|
bottomNavigationView.selectedItemId = it.displayMode.toMenuId()
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary ->
|
viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary ->
|
||||||
onGroupChange(groupSummary.orNull())
|
onGroupChange(groupSummary.orNull())
|
||||||
}
|
}
|
||||||
|
@ -127,7 +132,6 @@ class HomeDetailFragment @Inject constructor(
|
||||||
private fun setupBottomNavigationView() {
|
private fun setupBottomNavigationView() {
|
||||||
bottomNavigationView.setOnNavigationItemSelectedListener {
|
bottomNavigationView.setOnNavigationItemSelectedListener {
|
||||||
val displayMode = when (it.itemId) {
|
val displayMode = when (it.itemId) {
|
||||||
R.id.bottom_action_home -> RoomListDisplayMode.HOME
|
|
||||||
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
|
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
|
||||||
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
|
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
|
||||||
else -> RoomListDisplayMode.HOME
|
else -> RoomListDisplayMode.HOME
|
||||||
|
@ -149,12 +153,6 @@ class HomeDetailFragment @Inject constructor(
|
||||||
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
|
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
|
||||||
groupToolbarTitleView.setText(displayMode.titleRes)
|
groupToolbarTitleView.setText(displayMode.titleRes)
|
||||||
updateSelectedFragment(displayMode)
|
updateSelectedFragment(displayMode)
|
||||||
// Update the navigation view (for when we restore the tabs)
|
|
||||||
bottomNavigationView.selectedItemId = when (displayMode) {
|
|
||||||
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
|
||||||
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
|
||||||
else -> R.id.bottom_action_home
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
|
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
|
||||||
|
@ -194,4 +192,10 @@ class HomeDetailFragment @Inject constructor(
|
||||||
unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms))
|
unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms))
|
||||||
syncStateView.render(it.syncState)
|
syncStateView.render(it.syncState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RoomListDisplayMode.toMenuId() = when (this) {
|
||||||
|
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
||||||
|
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
||||||
|
else -> R.id.bottom_action_home
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class HomeDrawerFragment @Inject constructor(
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
|
replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
|
||||||
}
|
}
|
||||||
session.liveUser(session.myUserId).observeK(this) { optionalUser ->
|
session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser ->
|
||||||
val user = optionalUser?.getOrNull()
|
val user = optionalUser?.getOrNull()
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
avatarRenderer.render(user.toMatrixItem(), homeDrawerHeaderAvatarView)
|
avatarRenderer.render(user.toMatrixItem(), homeDrawerHeaderAvatarView)
|
||||||
|
|
|
@ -24,7 +24,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.query.QueryStringValue
|
||||||
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.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
|
@ -96,6 +98,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeGroupSummaries() {
|
private fun observeGroupSummaries() {
|
||||||
|
val groupSummariesQueryParams = groupSummaryQueryParams {
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
displayName = QueryStringValue.IsNotEmpty
|
||||||
|
}
|
||||||
Observable.combineLatest<GroupSummary, List<GroupSummary>, List<GroupSummary>>(
|
Observable.combineLatest<GroupSummary, List<GroupSummary>, List<GroupSummary>>(
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
|
@ -109,9 +115,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||||
},
|
},
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveGroupSummaries()
|
.liveGroupSummaries(groupSummariesQueryParams),
|
||||||
// Keep only joined groups. Group invitations will be managed later
|
|
||||||
.map { it.filter { groupSummary -> groupSummary.membership == Membership.JOIN } },
|
|
||||||
BiFunction { allCommunityGroup, communityGroups ->
|
BiFunction { allCommunityGroup, communityGroups ->
|
||||||
listOf(allCommunityGroup) + communityGroups
|
listOf(allCommunityGroup) + communityGroups
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,11 @@ import android.widget.EditText
|
||||||
import com.otaliastudios.autocomplete.Autocomplete
|
import com.otaliastudios.autocomplete.Autocomplete
|
||||||
import com.otaliastudios.autocomplete.AutocompleteCallback
|
import com.otaliastudios.autocomplete.AutocompleteCallback
|
||||||
import com.otaliastudios.autocomplete.CharPolicy
|
import com.otaliastudios.autocomplete.CharPolicy
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
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.user.model.User
|
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
|
import im.vector.matrix.android.api.util.toRoomAliasMatrixItem
|
||||||
|
@ -36,24 +38,29 @@ import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresente
|
||||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
import im.vector.riotx.features.autocomplete.emoji.AutocompleteEmojiPresenter
|
import im.vector.riotx.features.autocomplete.emoji.AutocompleteEmojiPresenter
|
||||||
import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
||||||
|
import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter
|
||||||
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
|
||||||
import im.vector.riotx.features.html.PillImageSpan
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AutoCompleter @Inject constructor(
|
class AutoCompleter @AssistedInject constructor(
|
||||||
|
@Assisted val roomId: String,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
private val commandAutocompletePolicy: CommandAutocompletePolicy,
|
||||||
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
private val autocompleteCommandPresenter: AutocompleteCommandPresenter,
|
||||||
private val autocompleteUserPresenter: AutocompleteUserPresenter,
|
private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory,
|
||||||
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
|
private val autocompleteRoomPresenter: AutocompleteRoomPresenter,
|
||||||
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
|
private val autocompleteGroupPresenter: AutocompleteGroupPresenter,
|
||||||
private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
|
private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): AutoCompleter
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var editText: EditText
|
private lateinit var editText: EditText
|
||||||
|
|
||||||
fun enterSpecialMode() {
|
fun enterSpecialMode() {
|
||||||
|
@ -68,22 +75,14 @@ class AutoCompleter @Inject constructor(
|
||||||
GlideApp.with(editText)
|
GlideApp.with(editText)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setup(editText: EditText, listener: AutoCompleterListener) {
|
fun setup(editText: EditText) {
|
||||||
this.editText = editText
|
this.editText = editText
|
||||||
|
|
||||||
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, R.attr.riotx_background))
|
val backgroundDrawable = ColorDrawable(ThemeUtils.getColor(editText.context, R.attr.riotx_background))
|
||||||
|
|
||||||
setupCommands(backgroundDrawable, editText)
|
setupCommands(backgroundDrawable, editText)
|
||||||
setupUsers(backgroundDrawable, editText, listener)
|
setupMembers(backgroundDrawable, editText)
|
||||||
setupRooms(backgroundDrawable, editText, listener)
|
setupGroups(backgroundDrawable, editText)
|
||||||
setupGroups(backgroundDrawable, editText, listener)
|
|
||||||
setupEmojis(backgroundDrawable, editText)
|
setupEmojis(backgroundDrawable, editText)
|
||||||
}
|
setupRooms(backgroundDrawable, editText)
|
||||||
|
|
||||||
fun render(state: TextComposerViewState) {
|
|
||||||
autocompleteUserPresenter.render(state.asyncUsers)
|
|
||||||
autocompleteRoomPresenter.render(state.asyncRooms)
|
|
||||||
autocompleteGroupPresenter.render(state.asyncGroups)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCommands(backgroundDrawable: Drawable, editText: EditText) {
|
private fun setupCommands(backgroundDrawable: Drawable, editText: EditText) {
|
||||||
|
@ -107,15 +106,15 @@ class AutoCompleter @Inject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUsers(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteUserPresenter.Callback) {
|
private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
|
||||||
autocompleteUserPresenter.callback = listener
|
val autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
|
||||||
Autocomplete.on<User>(editText)
|
Autocomplete.on<RoomMember>(editText)
|
||||||
.with(CharPolicy('@', true))
|
.with(CharPolicy('@', true))
|
||||||
.with(autocompleteUserPresenter)
|
.with(autocompleteMemberPresenter)
|
||||||
.with(ELEVATION)
|
.with(ELEVATION)
|
||||||
.with(backgroundDrawable)
|
.with(backgroundDrawable)
|
||||||
.with(object : AutocompleteCallback<User> {
|
.with(object : AutocompleteCallback<RoomMember> {
|
||||||
override fun onPopupItemClicked(editable: Editable, item: User): Boolean {
|
override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean {
|
||||||
insertMatrixItem(editText, editable, "@", item.toMatrixItem())
|
insertMatrixItem(editText, editable, "@", item.toMatrixItem())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -126,8 +125,7 @@ class AutoCompleter @Inject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteRoomPresenter.Callback) {
|
private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText) {
|
||||||
autocompleteRoomPresenter.callback = listener
|
|
||||||
Autocomplete.on<RoomSummary>(editText)
|
Autocomplete.on<RoomSummary>(editText)
|
||||||
.with(CharPolicy('#', true))
|
.with(CharPolicy('#', true))
|
||||||
.with(autocompleteRoomPresenter)
|
.with(autocompleteRoomPresenter)
|
||||||
|
@ -145,8 +143,7 @@ class AutoCompleter @Inject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText, listener: AutocompleteGroupPresenter.Callback) {
|
private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText) {
|
||||||
autocompleteGroupPresenter.callback = listener
|
|
||||||
Autocomplete.on<GroupSummary>(editText)
|
Autocomplete.on<GroupSummary>(editText)
|
||||||
.with(CharPolicy('+', true))
|
.with(CharPolicy('+', true))
|
||||||
.with(autocompleteGroupPresenter)
|
.with(autocompleteGroupPresenter)
|
||||||
|
@ -226,11 +223,6 @@ class AutoCompleter @Inject constructor(
|
||||||
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AutoCompleterListener :
|
|
||||||
AutocompleteUserPresenter.Callback,
|
|
||||||
AutocompleteRoomPresenter.Callback,
|
|
||||||
AutocompleteGroupPresenter.Callback
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ELEVATION = 6f
|
private const val ELEVATION = 6f
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,7 @@ import im.vector.riotx.features.attachments.ContactAttachment
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
|
||||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
|
||||||
|
@ -127,17 +124,15 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val timelineEventController: TimelineEventController,
|
private val timelineEventController: TimelineEventController,
|
||||||
private val autoCompleter: AutoCompleter,
|
autoCompleterFactory: AutoCompleter.Factory,
|
||||||
private val permalinkHandler: PermalinkHandler,
|
private val permalinkHandler: PermalinkHandler,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
||||||
val textComposerViewModelFactory: TextComposerViewModel.Factory,
|
|
||||||
private val eventHtmlRenderer: EventHtmlRenderer,
|
private val eventHtmlRenderer: EventHtmlRenderer,
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences
|
||||||
) :
|
) :
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
AutoCompleter.AutoCompleterListener,
|
|
||||||
VectorInviteView.Callback,
|
VectorInviteView.Callback,
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
|
@ -167,9 +162,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
GlideApp.with(this)
|
GlideApp.with(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val autoCompleter: AutoCompleter by lazy {
|
||||||
|
autoCompleterFactory.create(roomDetailArgs.roomId)
|
||||||
|
}
|
||||||
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
|
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
|
||||||
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
|
|
||||||
|
|
||||||
private val debouncer = Debouncer(createUIHandler())
|
private val debouncer = Debouncer(createUIHandler())
|
||||||
|
|
||||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||||
|
@ -205,9 +201,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
setupNotificationView()
|
setupNotificationView()
|
||||||
setupJumpToReadMarkerView()
|
setupJumpToReadMarkerView()
|
||||||
setupJumpToBottomView()
|
setupJumpToBottomView()
|
||||||
|
|
||||||
roomDetailViewModel.subscribe { renderState(it) }
|
roomDetailViewModel.subscribe { renderState(it) }
|
||||||
textComposerViewModel.subscribe { renderTextComposerState(it) }
|
roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) }
|
||||||
roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) }
|
|
||||||
|
|
||||||
roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair ->
|
roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair ->
|
||||||
val message = requireContext().getString(pair.first, *pair.second.toTypedArray())
|
val message = requireContext().getString(pair.first, *pair.second.toTypedArray())
|
||||||
|
@ -250,9 +246,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode ->
|
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode ->
|
||||||
when (mode) {
|
when (mode) {
|
||||||
is SendMode.REGULAR -> renderRegularMode(mode.text)
|
is SendMode.REGULAR -> renderRegularMode(mode.text)
|
||||||
is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
|
is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text)
|
||||||
is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
|
is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text)
|
||||||
is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
|
is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,9 +275,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
when (val sharedData = roomDetailArgs.sharedData) {
|
when (val sharedData = roomDetailArgs.sharedData) {
|
||||||
is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false))
|
is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false))
|
||||||
is SharedData.Attachments -> roomDetailViewModel.handle(RoomDetailAction.SendMedia(sharedData.attachmentData))
|
is SharedData.Attachments -> roomDetailViewModel.handle(RoomDetailAction.SendMedia(sharedData.attachmentData))
|
||||||
null -> Timber.v("No share data to process")
|
null -> Timber.v("No share data to process")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,12 +301,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
jumpToBottomView.setOnClickListener {
|
jumpToBottomView.setOnClickListener {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
||||||
jumpToBottomView.visibility = View.INVISIBLE
|
jumpToBottomView.visibility = View.INVISIBLE
|
||||||
withState(roomDetailViewModel) { state ->
|
if (!roomDetailViewModel.timeline.isLive) {
|
||||||
if (state.timeline?.isLive == false) {
|
roomDetailViewModel.timeline.restartWithEventId(null)
|
||||||
state.timeline.restartWithEventId(null)
|
} else {
|
||||||
} else {
|
layoutManager.scrollToPosition(0)
|
||||||
layoutManager.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +412,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
|
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
|
||||||
|
|
||||||
avatarRenderer.render(
|
avatarRenderer.render(
|
||||||
MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
|
MatrixItem.UserItem(event.root.senderId
|
||||||
|
?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
|
||||||
composerLayout.composerRelatedMessageAvatar
|
composerLayout.composerRelatedMessageAvatar
|
||||||
)
|
)
|
||||||
composerLayout.expand {
|
composerLayout.expand {
|
||||||
|
@ -468,6 +463,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
|
timelineEventController.callback = this
|
||||||
|
timelineEventController.timeline = roomDetailViewModel.timeline
|
||||||
|
|
||||||
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
|
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
|
||||||
epoxyVisibilityTracker.attach(recyclerView)
|
epoxyVisibilityTracker.attach(recyclerView)
|
||||||
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||||
|
@ -487,8 +485,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
timelineEventController.addModelBuildListener(modelBuildListener)
|
timelineEventController.addModelBuildListener(modelBuildListener)
|
||||||
recyclerView.adapter = timelineEventController.adapter
|
recyclerView.adapter = timelineEventController.adapter
|
||||||
|
|
||||||
timelineEventController.callback = this
|
|
||||||
|
|
||||||
if (vectorPreferences.swipeToReplyIsEnabled()) {
|
if (vectorPreferences.swipeToReplyIsEnabled()) {
|
||||||
val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
||||||
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
||||||
|
@ -505,7 +501,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
is MessageTextItem -> {
|
is MessageTextItem -> {
|
||||||
return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED
|
return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,9 +516,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
withState(roomDetailViewModel) {
|
withState(roomDetailViewModel) {
|
||||||
val showJumpToUnreadBanner = when (it.unreadState) {
|
val showJumpToUnreadBanner = when (it.unreadState) {
|
||||||
UnreadState.Unknown,
|
UnreadState.Unknown,
|
||||||
UnreadState.HasNoUnread -> false
|
UnreadState.HasNoUnread -> false
|
||||||
is UnreadState.ReadMarkerNotLoaded -> true
|
is UnreadState.ReadMarkerNotLoaded -> true
|
||||||
is UnreadState.HasUnread -> {
|
is UnreadState.HasUnread -> {
|
||||||
if (it.canShowJumpToReadMarker) {
|
if (it.canShowJumpToReadMarker) {
|
||||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
||||||
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
|
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
|
||||||
|
@ -536,14 +532,13 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
|
jumpToReadMarkerView?.isVisible = showJumpToUnreadBanner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupComposer() {
|
private fun setupComposer() {
|
||||||
autoCompleter.setup(composerLayout.composerEditText, this)
|
autoCompleter.setup(composerLayout.composerEditText)
|
||||||
|
|
||||||
composerLayout.callback = object : TextComposerView.Callback {
|
composerLayout.callback = object : TextComposerView.Callback {
|
||||||
override fun onAddAttachment() {
|
override fun onAddAttachment() {
|
||||||
if (!::attachmentTypeSelector.isInitialized) {
|
if (!::attachmentTypeSelector.isInitialized) {
|
||||||
|
@ -598,7 +593,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
val summary = state.asyncRoomSummary()
|
val summary = state.asyncRoomSummary()
|
||||||
val inviter = state.asyncInviter()
|
val inviter = state.asyncInviter()
|
||||||
if (summary?.membership == Membership.JOIN) {
|
if (summary?.membership == Membership.JOIN) {
|
||||||
scrollOnHighlightedEventCallback.timeline = state.timeline
|
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||||
timelineEventController.update(state)
|
timelineEventController.update(state)
|
||||||
inviteView.visibility = View.GONE
|
inviteView.visibility = View.GONE
|
||||||
val uid = session.myUserId
|
val uid = session.myUserId
|
||||||
|
@ -613,9 +608,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
} else if (state.asyncInviter.complete) {
|
} else if (state.asyncInviter.complete) {
|
||||||
vectorBaseActivity.finish()
|
vectorBaseActivity.finish()
|
||||||
}
|
}
|
||||||
|
val isRoomEncrypted = summary?.isEncrypted ?: false
|
||||||
if (state.tombstoneEvent == null) {
|
if (state.tombstoneEvent == null) {
|
||||||
composerLayout.visibility = View.VISIBLE
|
composerLayout.visibility = View.VISIBLE
|
||||||
composerLayout.setRoomEncrypted(state.isEncrypted)
|
composerLayout.setRoomEncrypted(isRoomEncrypted)
|
||||||
notificationAreaView.render(NotificationAreaView.State.Hidden)
|
notificationAreaView.render(NotificationAreaView.State.Hidden)
|
||||||
} else {
|
} else {
|
||||||
composerLayout.visibility = View.GONE
|
composerLayout.visibility = View.GONE
|
||||||
|
@ -638,10 +634,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTextComposerState(state: TextComposerViewState) {
|
|
||||||
autoCompleter.render(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderTombstoneEventHandling(async: Async<String>) {
|
private fun renderTombstoneEventHandling(async: Async<String>) {
|
||||||
when (async) {
|
when (async) {
|
||||||
is Loading -> {
|
is Loading -> {
|
||||||
|
@ -654,7 +646,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
navigator.openRoom(vectorBaseActivity, async())
|
navigator.openRoom(vectorBaseActivity, async())
|
||||||
vectorBaseActivity.finish()
|
vectorBaseActivity.finish()
|
||||||
}
|
}
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
vectorBaseActivity.hideWaitingView()
|
vectorBaseActivity.hideWaitingView()
|
||||||
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
|
vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
|
||||||
}
|
}
|
||||||
|
@ -663,23 +655,23 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
|
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
|
||||||
when (sendMessageResult) {
|
when (sendMessageResult) {
|
||||||
is SendMessageResult.MessageSent -> {
|
is SendMessageResult.MessageSent -> {
|
||||||
updateComposerText("")
|
updateComposerText("")
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandHandled -> {
|
is SendMessageResult.SlashCommandHandled -> {
|
||||||
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
|
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
|
||||||
updateComposerText("")
|
updateComposerText("")
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandError -> {
|
is SendMessageResult.SlashCommandError -> {
|
||||||
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
|
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandUnknown -> {
|
is SendMessageResult.SlashCommandUnknown -> {
|
||||||
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
|
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandResultOk -> {
|
is SendMessageResult.SlashCommandResultOk -> {
|
||||||
updateComposerText("")
|
updateComposerText("")
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandResultError -> {
|
is SendMessageResult.SlashCommandResultError -> {
|
||||||
displayCommandError(sendMessageResult.throwable.localizedMessage)
|
displayCommandError(sendMessageResult.throwable.localizedMessage)
|
||||||
}
|
}
|
||||||
is SendMessageResult.SlashCommandNotImplemented -> {
|
is SendMessageResult.SlashCommandNotImplemented -> {
|
||||||
|
@ -717,7 +709,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private fun displayRoomDetailActionResult(result: Async<RoomDetailAction>) {
|
private fun displayRoomDetailActionResult(result: Async<RoomDetailAction>) {
|
||||||
when (result) {
|
when (result) {
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
.setMessage(errorFormatter.toHumanReadable(result.error))
|
.setMessage(errorFormatter.toHumanReadable(result.error))
|
||||||
|
@ -728,7 +720,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
when (val data = result.invoke()) {
|
when (val data = result.invoke()) {
|
||||||
is RoomDetailAction.ReportContent -> {
|
is RoomDetailAction.ReportContent -> {
|
||||||
when {
|
when {
|
||||||
data.spam -> {
|
data.spam -> {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.content_reported_as_spam_title)
|
.setTitle(R.string.content_reported_as_spam_title)
|
||||||
.setMessage(R.string.content_reported_as_spam_content)
|
.setMessage(R.string.content_reported_as_spam_content)
|
||||||
|
@ -750,7 +742,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.content_reported_title)
|
.setTitle(R.string.content_reported_title)
|
||||||
.setMessage(R.string.content_reported_content)
|
.setMessage(R.string.content_reported_content)
|
||||||
|
@ -863,14 +855,14 @@ class RoomDetailFragment @Inject constructor(
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> {
|
PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> {
|
||||||
val action = roomDetailViewModel.pendingAction
|
val action = roomDetailViewModel.pendingAction
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
roomDetailViewModel.pendingAction = null
|
roomDetailViewModel.pendingAction = null
|
||||||
roomDetailViewModel.handle(action)
|
roomDetailViewModel.handle(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PERMISSION_REQUEST_CODE_INCOMING_URI -> {
|
PERMISSION_REQUEST_CODE_INCOMING_URI -> {
|
||||||
val pendingUri = roomDetailViewModel.pendingUri
|
val pendingUri = roomDetailViewModel.pendingUri
|
||||||
if (pendingUri != null) {
|
if (pendingUri != null) {
|
||||||
roomDetailViewModel.pendingUri = null
|
roomDetailViewModel.pendingUri = null
|
||||||
|
@ -966,43 +958,25 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
|
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutocompleteUserPresenter.Callback
|
|
||||||
|
|
||||||
override fun onQueryUsers(query: CharSequence?) {
|
|
||||||
textComposerViewModel.handle(TextComposerAction.QueryUsers(query))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutocompleteRoomPresenter.Callback
|
|
||||||
|
|
||||||
override fun onQueryRooms(query: CharSequence?) {
|
|
||||||
textComposerViewModel.handle(TextComposerAction.QueryRooms(query))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutocompleteGroupPresenter.Callback
|
|
||||||
|
|
||||||
override fun onQueryGroups(query: CharSequence?) {
|
|
||||||
textComposerViewModel.handle(TextComposerAction.QueryGroups(query))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleActions(action: EventSharedAction) {
|
private fun handleActions(action: EventSharedAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is EventSharedAction.AddReaction -> {
|
is EventSharedAction.AddReaction -> {
|
||||||
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
is EventSharedAction.ViewReactions -> {
|
is EventSharedAction.ViewReactions -> {
|
||||||
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
|
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
|
||||||
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
|
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
|
||||||
}
|
}
|
||||||
is EventSharedAction.Copy -> {
|
is EventSharedAction.Copy -> {
|
||||||
// I need info about the current selected message :/
|
// I need info about the current selected message :/
|
||||||
copyToClipboard(requireContext(), action.content, false)
|
copyToClipboard(requireContext(), action.content, false)
|
||||||
val msg = requireContext().getString(R.string.copied_to_clipboard)
|
val msg = requireContext().getString(R.string.copied_to_clipboard)
|
||||||
showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
|
showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
is EventSharedAction.Delete -> {
|
is EventSharedAction.Delete -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason)))
|
||||||
}
|
}
|
||||||
is EventSharedAction.Share -> {
|
is EventSharedAction.Share -> {
|
||||||
// TODO current data communication is too limited
|
// TODO current data communication is too limited
|
||||||
// Need to now the media type
|
// Need to now the media type
|
||||||
// TODO bad, just POC
|
// TODO bad, just POC
|
||||||
|
@ -1030,10 +1004,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is EventSharedAction.ViewEditHistory -> {
|
is EventSharedAction.ViewEditHistory -> {
|
||||||
onEditedDecorationClicked(action.messageInformationData)
|
onEditedDecorationClicked(action.messageInformationData)
|
||||||
}
|
}
|
||||||
is EventSharedAction.ViewSource -> {
|
is EventSharedAction.ViewSource -> {
|
||||||
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
||||||
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
||||||
it.text = action.content
|
it.text = action.content
|
||||||
|
@ -1044,7 +1018,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
is EventSharedAction.ViewDecryptedSource -> {
|
is EventSharedAction.ViewDecryptedSource -> {
|
||||||
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
|
||||||
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
view.findViewById<TextView>(R.id.event_content_text_view)?.let {
|
||||||
it.text = action.content
|
it.text = action.content
|
||||||
|
@ -1055,31 +1029,31 @@ class RoomDetailFragment @Inject constructor(
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
is EventSharedAction.QuickReact -> {
|
is EventSharedAction.QuickReact -> {
|
||||||
// eventId,ClickedOn,Add
|
// eventId,ClickedOn,Add
|
||||||
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
|
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
|
||||||
}
|
}
|
||||||
is EventSharedAction.Edit -> {
|
is EventSharedAction.Edit -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString()))
|
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString()))
|
||||||
}
|
}
|
||||||
is EventSharedAction.Quote -> {
|
is EventSharedAction.Quote -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
|
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
|
||||||
}
|
}
|
||||||
is EventSharedAction.Reply -> {
|
is EventSharedAction.Reply -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
|
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
|
||||||
}
|
}
|
||||||
is EventSharedAction.CopyPermalink -> {
|
is EventSharedAction.CopyPermalink -> {
|
||||||
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)
|
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)
|
||||||
copyToClipboard(requireContext(), permalink, false)
|
copyToClipboard(requireContext(), permalink, false)
|
||||||
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
is EventSharedAction.Resend -> {
|
is EventSharedAction.Resend -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
|
roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
|
||||||
}
|
}
|
||||||
is EventSharedAction.Remove -> {
|
is EventSharedAction.Remove -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
|
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
|
||||||
}
|
}
|
||||||
is EventSharedAction.ReportContentSpam -> {
|
is EventSharedAction.ReportContentSpam -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
|
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
|
||||||
action.eventId, action.senderId, "This message is spam", spam = true))
|
action.eventId, action.senderId, "This message is spam", spam = true))
|
||||||
}
|
}
|
||||||
|
@ -1087,19 +1061,19 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
|
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
|
||||||
action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
|
action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
|
||||||
}
|
}
|
||||||
is EventSharedAction.ReportContentCustom -> {
|
is EventSharedAction.ReportContentCustom -> {
|
||||||
promptReasonToReportContent(action)
|
promptReasonToReportContent(action)
|
||||||
}
|
}
|
||||||
is EventSharedAction.IgnoreUser -> {
|
is EventSharedAction.IgnoreUser -> {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(action.senderId))
|
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(action.senderId))
|
||||||
}
|
}
|
||||||
is EventSharedAction.OnUrlClicked -> {
|
is EventSharedAction.OnUrlClicked -> {
|
||||||
onUrlClicked(action.url)
|
onUrlClicked(action.url)
|
||||||
}
|
}
|
||||||
is EventSharedAction.OnUrlLongClicked -> {
|
is EventSharedAction.OnUrlLongClicked -> {
|
||||||
onUrlLongClicked(action.url)
|
onUrlLongClicked(action.url)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show()
|
Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1207,10 +1181,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
|
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
|
||||||
when (type) {
|
when (type) {
|
||||||
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera()
|
AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera()
|
||||||
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile()
|
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile()
|
||||||
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
|
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
|
||||||
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio()
|
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio()
|
||||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
|
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
|
||||||
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,18 @@ import android.net.Uri
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay2.PublishRelay
|
import com.jakewharton.rxrelay2.PublishRelay
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
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.isImageMessage
|
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||||
|
@ -89,20 +93,21 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||||
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||||
TimelineSettings(30,
|
TimelineSettings(30,
|
||||||
filterEdits = false,
|
filterEdits = false,
|
||||||
filterTypes = true,
|
filterTypes = true,
|
||||||
allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES,
|
allowedTypes = TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES,
|
||||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
||||||
} else {
|
} else {
|
||||||
TimelineSettings(30,
|
TimelineSettings(30,
|
||||||
filterEdits = true,
|
filterEdits = true,
|
||||||
filterTypes = true,
|
filterTypes = true,
|
||||||
allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
|
allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
|
||||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
||||||
}
|
}
|
||||||
|
|
||||||
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
||||||
private var timeline = room.createTimeline(eventId, timelineSettings)
|
var timeline = room.createTimeline(eventId, timelineSettings)
|
||||||
|
private set
|
||||||
|
|
||||||
private val _viewEvents = PublishDataSource<RoomDetailViewEvents>()
|
private val _viewEvents = PublishDataSource<RoomDetailViewEvents>()
|
||||||
val viewEvents: DataSource<RoomDetailViewEvents> = _viewEvents
|
val viewEvents: DataSource<RoomDetailViewEvents> = _viewEvents
|
||||||
|
@ -138,18 +143,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
timeline.start()
|
||||||
|
timeline.addListener(this)
|
||||||
|
observeRoomSummary()
|
||||||
|
observeSummaryState()
|
||||||
getUnreadState()
|
getUnreadState()
|
||||||
observeSyncState()
|
observeSyncState()
|
||||||
observeRoomSummary()
|
|
||||||
observeEventDisplayedActions()
|
observeEventDisplayedActions()
|
||||||
observeSummaryState()
|
|
||||||
observeDrafts()
|
observeDrafts()
|
||||||
observeUnreadState()
|
observeUnreadState()
|
||||||
|
room.getRoomSummaryLive()
|
||||||
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
|
||||||
timeline.addListener(this)
|
|
||||||
timeline.start()
|
|
||||||
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
|
||||||
|
|
||||||
// Inform the SDK that the room is displayed
|
// Inform the SDK that the room is displayed
|
||||||
session.onRoomDisplayed(initialState.roomId)
|
session.onRoomDisplayed(initialState.roomId)
|
||||||
}
|
}
|
||||||
|
@ -233,23 +237,23 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
copy(
|
copy(
|
||||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||||
sendMode = when (draft) {
|
sendMode = when (draft) {
|
||||||
is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
|
is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
|
||||||
is UserDraft.QUOTE -> {
|
is UserDraft.QUOTE -> {
|
||||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.QUOTE(timelineEvent, draft.text)
|
SendMode.QUOTE(timelineEvent, draft.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserDraft.REPLY -> {
|
is UserDraft.REPLY -> {
|
||||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.REPLY(timelineEvent, draft.text)
|
SendMode.REPLY(timelineEvent, draft.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserDraft.EDIT -> {
|
is UserDraft.EDIT -> {
|
||||||
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
|
||||||
SendMode.EDIT(timelineEvent, draft.text)
|
SendMode.EDIT(timelineEvent, draft.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: SendMode.REGULAR("")
|
} ?: SendMode.REGULAR("")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,7 +262,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
|
|
||||||
private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) {
|
private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) {
|
||||||
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val roomId = tombstoneContent.replacementRoom ?: ""
|
val roomId = tombstoneContent.replacementRoom ?: ""
|
||||||
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
||||||
|
@ -310,7 +314,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
private fun handleSendMessage(action: RoomDetailAction.SendMessage) {
|
private fun handleSendMessage(action: RoomDetailAction.SendMessage) {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
|
@ -396,7 +400,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is SendMode.EDIT -> {
|
is SendMode.EDIT -> {
|
||||||
// is original event a reply?
|
// is original event a reply?
|
||||||
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||||
if (inReplyTo != null) {
|
if (inReplyTo != null) {
|
||||||
// TODO check if same content?
|
// TODO check if same content?
|
||||||
room.getTimeLineEvent(inReplyTo)?.let {
|
room.getTimeLineEvent(inReplyTo)?.let {
|
||||||
|
@ -405,13 +409,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
} else {
|
} else {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val existingBody = messageContent?.body ?: ""
|
val existingBody = messageContent?.body ?: ""
|
||||||
if (existingBody != action.text) {
|
if (existingBody != action.text) {
|
||||||
room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "",
|
room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "",
|
||||||
messageContent?.type ?: MessageType.MSGTYPE_TEXT,
|
messageContent?.type ?: MessageType.MSGTYPE_TEXT,
|
||||||
action.text,
|
action.text,
|
||||||
action.autoMarkdown)
|
action.autoMarkdown)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Same message content, do not send edition")
|
Timber.w("Same message content, do not send edition")
|
||||||
}
|
}
|
||||||
|
@ -422,7 +426,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is SendMode.QUOTE -> {
|
is SendMode.QUOTE -> {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val textMsg = messageContent?.body
|
val textMsg = messageContent?.body
|
||||||
|
|
||||||
val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
|
val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
|
||||||
|
@ -538,7 +542,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
|
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
|
||||||
null -> room.sendMedias(attachments)
|
null -> room.sendMedias(attachments)
|
||||||
else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name
|
else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name
|
||||||
?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
|
?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -728,7 +732,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.subscribeBy(onNext = { actions ->
|
.subscribeBy(onNext = { actions ->
|
||||||
val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event
|
val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event
|
||||||
?: return@subscribeBy
|
?: return@subscribeBy
|
||||||
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
|
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
|
||||||
if (trackUnreadMessages.get()) {
|
if (trackUnreadMessages.get()) {
|
||||||
if (globalMostRecentDisplayedEvent == null) {
|
if (globalMostRecentDisplayedEvent == null) {
|
||||||
|
@ -791,10 +795,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
room.rx().liveRoomSummary()
|
room.rx().liveRoomSummary()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(
|
copy(asyncRoomSummary = async)
|
||||||
asyncRoomSummary = async,
|
|
||||||
isEncrypted = room.isEncrypted()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -880,7 +881,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
timeline.removeListener(this)
|
timeline.removeAllListeners()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
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.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
|
||||||
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.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
@ -51,11 +50,9 @@ sealed class UnreadState {
|
||||||
data class RoomDetailViewState(
|
data class RoomDetailViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val eventId: String?,
|
val eventId: String?,
|
||||||
val timeline: Timeline? = null,
|
|
||||||
val asyncInviter: Async<User> = Uninitialized,
|
val asyncInviter: Async<User> = Uninitialized,
|
||||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val sendMode: SendMode = SendMode.REGULAR(""),
|
val sendMode: SendMode = SendMode.REGULAR(""),
|
||||||
val isEncrypted: Boolean = false,
|
|
||||||
val tombstoneEvent: Event? = null,
|
val tombstoneEvent: Event? = null,
|
||||||
val tombstoneEventHandling: Async<String> = Uninitialized,
|
val tombstoneEventHandling: Async<String> = Uninitialized,
|
||||||
val syncState: SyncState = SyncState.Idle,
|
val syncState: SyncState = SyncState.Idle,
|
||||||
|
|
|
@ -1,158 +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.riotx.features.home.room.detail.composer
|
|
||||||
|
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
|
||||||
import im.vector.matrix.android.api.session.Session
|
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
|
||||||
import im.vector.matrix.rx.rx
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.functions.BiFunction
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
typealias AutocompleteQuery = CharSequence
|
|
||||||
|
|
||||||
class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState,
|
|
||||||
private val session: Session
|
|
||||||
) : VectorViewModel<TextComposerViewState, TextComposerAction>(initialState) {
|
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
|
||||||
|
|
||||||
private val usersQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
|
||||||
private val roomsQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
|
||||||
private val groupsQueryObservable = BehaviorRelay.create<Option<AutocompleteQuery>>()
|
|
||||||
|
|
||||||
@AssistedInject.Factory
|
|
||||||
interface Factory {
|
|
||||||
fun create(initialState: TextComposerViewState): TextComposerViewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<TextComposerViewModel, TextComposerViewState> {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel? {
|
|
||||||
val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
|
||||||
return fragment.textComposerViewModelFactory.create(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
observeUsersQuery()
|
|
||||||
observeRoomsQuery()
|
|
||||||
observeGroupsQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: TextComposerAction) {
|
|
||||||
when (action) {
|
|
||||||
is TextComposerAction.QueryUsers -> handleQueryUsers(action)
|
|
||||||
is TextComposerAction.QueryRooms -> handleQueryRooms(action)
|
|
||||||
is TextComposerAction.QueryGroups -> handleQueryGroups(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleQueryUsers(action: TextComposerAction.QueryUsers) {
|
|
||||||
val query = Option.fromNullable(action.query)
|
|
||||||
usersQueryObservable.accept(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleQueryRooms(action: TextComposerAction.QueryRooms) {
|
|
||||||
val query = Option.fromNullable(action.query)
|
|
||||||
roomsQueryObservable.accept(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleQueryGroups(action: TextComposerAction.QueryGroups) {
|
|
||||||
val query = Option.fromNullable(action.query)
|
|
||||||
groupsQueryObservable.accept(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeUsersQuery() {
|
|
||||||
Observable.combineLatest<List<String>, Option<AutocompleteQuery>, List<User>>(
|
|
||||||
room.rx().liveRoomMemberIds(),
|
|
||||||
usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
|
||||||
BiFunction { roomMemberIds, query ->
|
|
||||||
val users = roomMemberIds.mapNotNull { session.getUser(it) }
|
|
||||||
|
|
||||||
val filter = query.orNull()
|
|
||||||
if (filter.isNullOrBlank()) {
|
|
||||||
users
|
|
||||||
} else {
|
|
||||||
users.filter {
|
|
||||||
it.displayName?.contains(filter, ignoreCase = true) ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sortedBy { it.displayName }
|
|
||||||
}
|
|
||||||
).execute { async ->
|
|
||||||
copy(
|
|
||||||
asyncUsers = async
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeRoomsQuery() {
|
|
||||||
Observable.combineLatest<List<RoomSummary>, Option<AutocompleteQuery>, List<RoomSummary>>(
|
|
||||||
session.rx().liveRoomSummaries(),
|
|
||||||
roomsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
|
||||||
BiFunction { roomSummaries, query ->
|
|
||||||
val filter = query.orNull() ?: ""
|
|
||||||
// Keep only room with a canonical alias
|
|
||||||
roomSummaries
|
|
||||||
.filter {
|
|
||||||
it.canonicalAlias?.contains(filter, ignoreCase = true) == true
|
|
||||||
}
|
|
||||||
.sortedBy { it.displayName }
|
|
||||||
}
|
|
||||||
).execute { async ->
|
|
||||||
copy(
|
|
||||||
asyncRooms = async
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeGroupsQuery() {
|
|
||||||
Observable.combineLatest<List<GroupSummary>, Option<AutocompleteQuery>, List<GroupSummary>>(
|
|
||||||
session.rx().liveGroupSummaries(),
|
|
||||||
groupsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS),
|
|
||||||
BiFunction { groupSummaries, query ->
|
|
||||||
val filter = query.orNull()
|
|
||||||
if (filter.isNullOrBlank()) {
|
|
||||||
groupSummaries
|
|
||||||
} else {
|
|
||||||
groupSummaries
|
|
||||||
.filter {
|
|
||||||
it.groupId.contains(filter, ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sortedBy { it.displayName }
|
|
||||||
}
|
|
||||||
).execute { async ->
|
|
||||||
copy(
|
|
||||||
asyncGroups = async
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +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.riotx.features.home.room.detail.composer
|
|
||||||
|
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.MvRxState
|
|
||||||
import com.airbnb.mvrx.Uninitialized
|
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
|
||||||
|
|
||||||
data class TextComposerViewState(val roomId: String,
|
|
||||||
val asyncUsers: Async<List<User>> = Uninitialized,
|
|
||||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
|
||||||
val asyncGroups: Async<List<GroupSummary>> = Uninitialized
|
|
||||||
) : MvRxState {
|
|
||||||
|
|
||||||
constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
|
|
||||||
}
|
|
|
@ -95,12 +95,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
private val modelCache = arrayListOf<CacheItemData?>()
|
private val modelCache = arrayListOf<CacheItemData?>()
|
||||||
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
||||||
private var inSubmitList: Boolean = false
|
private var inSubmitList: Boolean = false
|
||||||
private var timeline: Timeline? = null
|
|
||||||
private var unreadState: UnreadState = UnreadState.Unknown
|
private var unreadState: UnreadState = UnreadState.Unknown
|
||||||
private var positionOfReadMarker: Int? = null
|
private var positionOfReadMarker: Int? = null
|
||||||
private var eventIdToHighlight: String? = null
|
private var eventIdToHighlight: String? = null
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
var timeline: Timeline? = null
|
||||||
|
|
||||||
private val listUpdateCallback = object : ListUpdateCallback {
|
private val listUpdateCallback = object : ListUpdateCallback {
|
||||||
|
|
||||||
|
@ -176,10 +176,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(viewState: RoomDetailViewState) {
|
fun update(viewState: RoomDetailViewState) {
|
||||||
if (timeline?.timelineID != viewState.timeline?.timelineID) {
|
|
||||||
timeline = viewState.timeline
|
|
||||||
timeline?.addListener(this)
|
|
||||||
}
|
|
||||||
var requestModelBuild = false
|
var requestModelBuild = false
|
||||||
if (eventIdToHighlight != viewState.highlightedEventId) {
|
if (eventIdToHighlight != viewState.highlightedEventId) {
|
||||||
// Clear cache to force a refresh
|
// Clear cache to force a refresh
|
||||||
|
@ -205,6 +201,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
|
|
||||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
super.onAttachedToRecyclerView(recyclerView)
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
timeline?.addListener(this)
|
||||||
timelineMediaSizeProvider.recyclerView = recyclerView
|
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
state.actions()?.forEachIndexed { index, action ->
|
state.actions.forEachIndexed { index, action ->
|
||||||
if (action is EventSharedAction.Separator) {
|
if (action is EventSharedAction.Separator) {
|
||||||
bottomSheetSeparatorItem {
|
bottomSheetSeparatorItem {
|
||||||
id("separator_$index")
|
id("separator_$index")
|
||||||
|
|
|
@ -31,8 +31,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
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.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.matrix.rx.RxRoom
|
|
||||||
import im.vector.matrix.rx.unwrap
|
import im.vector.matrix.rx.unwrap
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.canReact
|
import im.vector.riotx.core.extensions.canReact
|
||||||
|
@ -42,8 +41,8 @@ import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventForm
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
import im.vector.riotx.features.html.VectorHtmlCompressor
|
import im.vector.riotx.features.html.VectorHtmlCompressor
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
|
||||||
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ data class MessageActionState(
|
||||||
// For quick reactions
|
// For quick reactions
|
||||||
val quickStates: Async<List<ToggleState>> = Uninitialized,
|
val quickStates: Async<List<ToggleState>> = Uninitialized,
|
||||||
// For actions
|
// For actions
|
||||||
val actions: Async<List<EventSharedAction>> = Uninitialized,
|
val actions: List<EventSharedAction> = emptyList(),
|
||||||
val expendedReportContentMenu: Boolean = false
|
val expendedReportContentMenu: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
@ -112,7 +111,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
init {
|
init {
|
||||||
observeEvent()
|
observeEvent()
|
||||||
observeReactions()
|
observeReactions()
|
||||||
observeEventAction()
|
observeTimelineEventState()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: MessageActionsAction) {
|
override fun handle(action: MessageActionsAction) {
|
||||||
|
@ -131,32 +130,17 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
private fun observeEvent() {
|
private fun observeEvent() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
RxRoom(room)
|
room.rx()
|
||||||
.liveTimelineEvent(eventId)
|
.liveTimelineEvent(eventId)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
copy(
|
copy(timelineEvent = it)
|
||||||
timelineEvent = it,
|
|
||||||
messageBody = computeMessageBody(it)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeEventAction() {
|
|
||||||
if (room == null) return
|
|
||||||
RxRoom(room)
|
|
||||||
.liveTimelineEvent(eventId)
|
|
||||||
.map {
|
|
||||||
actionsForEvent(it)
|
|
||||||
}
|
|
||||||
.execute {
|
|
||||||
copy(actions = it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeReactions() {
|
private fun observeReactions() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
RxRoom(room)
|
room.rx()
|
||||||
.liveAnnotationSummary(eventId)
|
.liveAnnotationSummary(eventId)
|
||||||
.map { annotations ->
|
.map { annotations ->
|
||||||
EmojiDataSource.quickEmojis.map { emoji ->
|
EmojiDataSource.quickEmojis.map { emoji ->
|
||||||
|
@ -168,11 +152,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeMessageBody(timelineEvent: Async<TimelineEvent>): CharSequence? {
|
private fun observeTimelineEventState() {
|
||||||
return when (timelineEvent()?.root?.getClearType()) {
|
asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent ->
|
||||||
|
val computedMessage = computeMessageBody(timelineEvent)
|
||||||
|
val actions = actionsForEvent(timelineEvent)
|
||||||
|
setState { copy(messageBody = computedMessage, actions = actions) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? {
|
||||||
|
return when (timelineEvent.root.getClearType()) {
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STICKER -> {
|
EventType.STICKER -> {
|
||||||
val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
|
val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
|
||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
val html = messageContent.formattedBody
|
val html = messageContent.formattedBody
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
|
@ -193,41 +185,39 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER -> {
|
EventType.CALL_ANSWER -> {
|
||||||
timelineEvent()?.let { noticeEventFormatter.format(it) }
|
noticeEventFormatter.format(timelineEvent)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(optionalEvent: Optional<TimelineEvent>): List<EventSharedAction> {
|
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {
|
||||||
val event = optionalEvent.getOrNull() ?: return emptyList()
|
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
|
?: timelineEvent.root.getClearContent().toModel()
|
||||||
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
|
|
||||||
?: event.root.getClearContent().toModel()
|
|
||||||
val type = messageContent?.type
|
val type = messageContent?.type
|
||||||
|
|
||||||
return arrayListOf<EventSharedAction>().apply {
|
return arrayListOf<EventSharedAction>().apply {
|
||||||
if (event.root.sendState.hasFailed()) {
|
if (timelineEvent.root.sendState.hasFailed()) {
|
||||||
if (canRetry(event)) {
|
if (canRetry(timelineEvent)) {
|
||||||
add(EventSharedAction.Resend(eventId))
|
add(EventSharedAction.Resend(eventId))
|
||||||
}
|
}
|
||||||
add(EventSharedAction.Remove(eventId))
|
add(EventSharedAction.Remove(eventId))
|
||||||
} else if (event.root.sendState.isSending()) {
|
} else if (timelineEvent.root.sendState.isSending()) {
|
||||||
// TODO is uploading attachment?
|
// TODO is uploading attachment?
|
||||||
if (canCancel(event)) {
|
if (canCancel(timelineEvent)) {
|
||||||
add(EventSharedAction.Cancel(eventId))
|
add(EventSharedAction.Cancel(eventId))
|
||||||
}
|
}
|
||||||
} else if (event.root.sendState == SendState.SYNCED) {
|
} else if (timelineEvent.root.sendState == SendState.SYNCED) {
|
||||||
if (!event.root.isRedacted()) {
|
if (!timelineEvent.root.isRedacted()) {
|
||||||
if (canReply(event, messageContent)) {
|
if (canReply(timelineEvent, messageContent)) {
|
||||||
add(EventSharedAction.Reply(eventId))
|
add(EventSharedAction.Reply(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canEdit(event, session.myUserId)) {
|
if (canEdit(timelineEvent, session.myUserId)) {
|
||||||
add(EventSharedAction.Edit(eventId))
|
add(EventSharedAction.Edit(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canRedact(event, session.myUserId)) {
|
if (canRedact(timelineEvent, session.myUserId)) {
|
||||||
add(EventSharedAction.Delete(eventId))
|
add(EventSharedAction.Delete(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,19 +226,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
add(EventSharedAction.Copy(messageContent!!.body))
|
add(EventSharedAction.Copy(messageContent!!.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.canReact()) {
|
if (timelineEvent.canReact()) {
|
||||||
add(EventSharedAction.AddReaction(eventId))
|
add(EventSharedAction.AddReaction(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canQuote(event, messageContent)) {
|
if (canQuote(timelineEvent, messageContent)) {
|
||||||
add(EventSharedAction.Quote(eventId))
|
add(EventSharedAction.Quote(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canViewReactions(event)) {
|
if (canViewReactions(timelineEvent)) {
|
||||||
add(EventSharedAction.ViewReactions(informationData))
|
add(EventSharedAction.ViewReactions(informationData))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.hasBeenEdited()) {
|
if (timelineEvent.hasBeenEdited()) {
|
||||||
add(EventSharedAction.ViewEditHistory(informationData))
|
add(EventSharedAction.ViewEditHistory(informationData))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +251,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.root.sendState == SendState.SENT) {
|
if (timelineEvent.root.sendState == SendState.SENT) {
|
||||||
// TODO Can be redacted
|
// TODO Can be redacted
|
||||||
|
|
||||||
// TODO sent by me or sufficient power level
|
// TODO sent by me or sufficient power level
|
||||||
|
@ -269,24 +259,22 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vectorPreferences.developerMode()) {
|
if (vectorPreferences.developerMode()) {
|
||||||
add(EventSharedAction.ViewSource(event.root.toContentStringWithIndent()))
|
add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent()))
|
||||||
if (event.isEncrypted()) {
|
if (timelineEvent.isEncrypted()) {
|
||||||
val decryptedContent = event.root.toClearContentStringWithIndent()
|
val decryptedContent = timelineEvent.root.toClearContentStringWithIndent()
|
||||||
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
||||||
add(EventSharedAction.ViewDecryptedSource(decryptedContent))
|
add(EventSharedAction.ViewDecryptedSource(decryptedContent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(EventSharedAction.CopyPermalink(eventId))
|
add(EventSharedAction.CopyPermalink(eventId))
|
||||||
|
if (session.myUserId != timelineEvent.root.senderId) {
|
||||||
if (session.myUserId != event.root.senderId) {
|
|
||||||
// not sent by me
|
// not sent by me
|
||||||
if (event.root.getClearType() == EventType.MESSAGE) {
|
if (timelineEvent.root.getClearType() == EventType.MESSAGE) {
|
||||||
add(EventSharedAction.ReportContent(eventId, event.root.senderId))
|
add(EventSharedAction.ReportContent(eventId, timelineEvent.root.senderId))
|
||||||
}
|
}
|
||||||
|
|
||||||
add(EventSharedAction.Separator)
|
add(EventSharedAction.Separator)
|
||||||
add(EventSharedAction.IgnoreUser(event.root.senderId))
|
add(EventSharedAction.IgnoreUser(timelineEvent.root.senderId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,8 +128,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatRoomMemberEvent(event: Event, senderName: String?): String? {
|
private fun formatRoomMemberEvent(event: Event, senderName: String?): String? {
|
||||||
val eventContent: RoomMember? = event.getClearContent().toModel()
|
val eventContent: RoomMemberContent? = event.getClearContent().toModel()
|
||||||
val prevEventContent: RoomMember? = event.prevContent.toModel()
|
val prevEventContent: RoomMemberContent? = event.prevContent.toModel()
|
||||||
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
|
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
|
||||||
return if (isMembershipEvent) {
|
return if (isMembershipEvent) {
|
||||||
buildMembershipNotice(event, senderName, eventContent, prevEventContent)
|
buildMembershipNotice(event, senderName, eventContent, prevEventContent)
|
||||||
|
@ -166,7 +166,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
|
?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String {
|
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String {
|
||||||
val displayText = StringBuilder()
|
val displayText = StringBuilder()
|
||||||
// Check display name has been changed
|
// Check display name has been changed
|
||||||
if (eventContent?.displayName != prevEventContent?.displayName) {
|
if (eventContent?.displayName != prevEventContent?.displayName) {
|
||||||
|
@ -198,7 +198,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||||
return displayText.toString()
|
return displayText.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
|
private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String? {
|
||||||
val senderDisplayName = senderName ?: event.senderId ?: ""
|
val senderDisplayName = senderName ?: event.senderId ?: ""
|
||||||
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: ""
|
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: ""
|
||||||
return when (eventContent?.membership) {
|
return when (eventContent?.membership) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
holder.messageView.setOnClickListener(attributes.itemClickListener)
|
holder.messageView.setOnClickListener(attributes.itemClickListener)
|
||||||
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
|
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
|
||||||
if (searchForPills) {
|
if (searchForPills) {
|
||||||
message?.findPillsAndProcess { it.bind(holder.messageView) }
|
message?.findPillsAndProcess(coroutineScope) { it.bind(holder.messageView) }
|
||||||
}
|
}
|
||||||
val textFuture = PrecomputedTextCompat.getTextFuture(
|
val textFuture = PrecomputedTextCompat.getTextFuture(
|
||||||
message ?: "",
|
message ?: "",
|
||||||
|
|
|
@ -25,14 +25,11 @@ import im.vector.riotx.core.linkify.VectorLinkify
|
||||||
import im.vector.riotx.core.utils.isValidUrl
|
import im.vector.riotx.core.utils.isValidUrl
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotx.features.html.PillImageSpan
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import me.saket.bettermovementmethod.BetterLinkMovementMethod
|
import me.saket.bettermovementmethod.BetterLinkMovementMethod
|
||||||
|
|
||||||
fun CharSequence.findPillsAndProcess(processBlock: (PillImageSpan) -> Unit) {
|
fun CharSequence.findPillsAndProcess(scope: CoroutineScope, processBlock: (PillImageSpan) -> Unit) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
toSpannable().let { spannable ->
|
toSpannable().let { spannable ->
|
||||||
spannable.getSpans(0, spannable.length, PillImageSpan::class.java)
|
spannable.getSpans(0, spannable.length, PillImageSpan::class.java)
|
||||||
|
|
|
@ -59,7 +59,8 @@ data class RoomListParams(
|
||||||
class RoomListFragment @Inject constructor(
|
class RoomListFragment @Inject constructor(
|
||||||
private val roomController: RoomSummaryController,
|
private val roomController: RoomSummaryController,
|
||||||
val roomListViewModelFactory: RoomListViewModel.Factory,
|
val roomListViewModelFactory: RoomListViewModel.Factory,
|
||||||
private val notificationDrawerManager: NotificationDrawerManager
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
|
private val sharedViewPool: RecyclerView.RecycledViewPool
|
||||||
|
|
||||||
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
||||||
|
|
||||||
|
@ -95,7 +96,6 @@ class RoomListFragment @Inject constructor(
|
||||||
setupCreateRoomButton()
|
setupCreateRoomButton()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||||
|
|
||||||
roomListViewModel.subscribe { renderState(it) }
|
roomListViewModel.subscribe { renderState(it) }
|
||||||
roomListViewModel.viewEvents
|
roomListViewModel.viewEvents
|
||||||
.observe()
|
.observe()
|
||||||
|
@ -193,6 +193,8 @@ class RoomListFragment @Inject constructor(
|
||||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
roomListView.layoutManager = layoutManager
|
roomListView.layoutManager = layoutManager
|
||||||
roomListView.itemAnimator = RoomListAnimator()
|
roomListView.itemAnimator = RoomListAnimator()
|
||||||
|
roomListView.setRecycledViewPool(sharedViewPool)
|
||||||
|
layoutManager.recycleChildrenOnDetach = true
|
||||||
roomController.listener = this
|
roomController.listener = this
|
||||||
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||||
roomController.addModelBuildListener(modelBuildListener)
|
roomController.addModelBuildListener(modelBuildListener)
|
||||||
|
|
|
@ -46,6 +46,7 @@ data class RoomListActionsArgs(
|
||||||
class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomListQuickActionsEpoxyController.Listener {
|
class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomListQuickActionsEpoxyController.Listener {
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
|
||||||
|
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
|
||||||
@Inject lateinit var roomListActionsViewModelFactory: RoomListQuickActionsViewModel.Factory
|
@Inject lateinit var roomListActionsViewModelFactory: RoomListQuickActionsViewModel.Factory
|
||||||
@Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController
|
@Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController
|
||||||
@Inject lateinit var navigator: Navigator
|
@Inject lateinit var navigator: Navigator
|
||||||
|
@ -70,7 +71,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||||
recyclerView.configureWith(roomListActionsEpoxyController, hasFixedSize = false)
|
recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false)
|
||||||
// Disable item animation
|
// Disable item animation
|
||||||
recyclerView.itemAnimator = null
|
recyclerView.itemAnimator = null
|
||||||
roomListActionsEpoxyController.listener = this
|
roomListActionsEpoxyController.listener = this
|
||||||
|
|
|
@ -88,25 +88,27 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun updateAvatarDrawable(drawable: Drawable?) {
|
internal fun updateAvatarDrawable(drawable: Drawable?) {
|
||||||
pillDrawable.apply {
|
pillDrawable.chipIcon = drawable
|
||||||
chipIcon = drawable
|
tv?.get()?.invalidate()
|
||||||
}
|
|
||||||
tv?.get()?.apply {
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun createChipDrawable(): ChipDrawable {
|
private fun createChipDrawable(): ChipDrawable {
|
||||||
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
|
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
|
||||||
|
val icon = try {
|
||||||
|
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
avatarRenderer.getPlaceholderDrawable(context, matrixItem)
|
||||||
|
}
|
||||||
|
|
||||||
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||||
text = matrixItem.getBestName()
|
text = matrixItem.getBestName()
|
||||||
textEndPadding = textPadding
|
textEndPadding = textPadding
|
||||||
textStartPadding = textPadding
|
textStartPadding = textPadding
|
||||||
setChipMinHeightResource(R.dimen.pill_min_height)
|
setChipMinHeightResource(R.dimen.pill_min_height)
|
||||||
setChipIconSizeResource(R.dimen.pill_avatar_size)
|
setChipIconSizeResource(R.dimen.pill_avatar_size)
|
||||||
chipIcon = avatarRenderer.getPlaceholderDrawable(context, matrixItem)
|
chipIcon = icon
|
||||||
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,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.toModel
|
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.api.session.room.model.RoomMemberContent
|
||||||
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.session.room.timeline.getEditedEventId
|
import im.vector.matrix.android.api.session.room.timeline.getEditedEventId
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageBody
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageBody
|
||||||
|
@ -163,7 +163,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
|
private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
|
||||||
val content = event.content?.toModel<RoomMember>() ?: return null
|
val content = event.content?.toModel<RoomMemberContent>() ?: return null
|
||||||
val roomId = event.roomId ?: return null
|
val roomId = event.roomId ?: return null
|
||||||
val dName = event.senderId?.let { session.getUser(it)?.displayName }
|
val dName = event.senderId?.let { session.getUser(it)?.displayName }
|
||||||
if (Membership.INVITE == content.membership) {
|
if (Membership.INVITE == content.membership) {
|
||||||
|
|
|
@ -18,10 +18,10 @@ package im.vector.riotx.features.reactions.data
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenScope
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ScreenScope
|
@Singleton
|
||||||
class EmojiDataSource @Inject constructor(
|
class EmojiDataSource @Inject constructor(
|
||||||
resources: Resources
|
resources: Resources
|
||||||
) {
|
) {
|
||||||
|
@ -41,6 +41,7 @@ class EmojiDataSource @Inject constructor(
|
||||||
|
|
||||||
// First add emojis with name matching query, sorted by name
|
// First add emojis with name matching query, sorted by name
|
||||||
return (rawData.emojis.values
|
return (rawData.emojis.values
|
||||||
|
.asSequence()
|
||||||
.filter { emojiItem ->
|
.filter { emojiItem ->
|
||||||
emojiItem.name.contains(query, true)
|
emojiItem.name.contains(query, true)
|
||||||
}
|
}
|
||||||
|
@ -55,6 +56,7 @@ class EmojiDataSource @Inject constructor(
|
||||||
.sortedBy { it.name })
|
.sortedBy { it.name })
|
||||||
// and ensure they will not be present twice
|
// and ensure they will not be present twice
|
||||||
.distinct()
|
.distinct()
|
||||||
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getQuickReactions(): List<EmojiItem> {
|
fun getQuickReactions(): List<EmojiItem> {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
|
||||||
|
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.extensions.postLiveEvent
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
|
@ -79,13 +80,14 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeJoinedRooms() {
|
private fun observeJoinedRooms() {
|
||||||
|
val queryParams = roomSummaryQueryParams {
|
||||||
|
memberships = listOf(Membership.JOIN)
|
||||||
|
}
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveRoomSummaries()
|
.liveRoomSummaries(queryParams)
|
||||||
.subscribe { list ->
|
.subscribe { list ->
|
||||||
val joinedRoomIds = list
|
val joinedRoomIds = list
|
||||||
// Keep only joined room
|
|
||||||
?.filter { it.membership == Membership.JOIN }
|
|
||||||
?.map { it.roomId }
|
?.map { it.roomId }
|
||||||
?.toSet()
|
?.toSet()
|
||||||
?: emptySet()
|
?: emptySet()
|
||||||
|
@ -106,9 +108,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
override fun handle(action: RoomDirectoryAction) {
|
override fun handle(action: RoomDirectoryAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action)
|
is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action)
|
||||||
is RoomDirectoryAction.FilterWith -> filterWith(action)
|
is RoomDirectoryAction.FilterWith -> filterWith(action)
|
||||||
RoomDirectoryAction.LoadMore -> loadMore()
|
RoomDirectoryAction.LoadMore -> loadMore()
|
||||||
is RoomDirectoryAction.JoinRoom -> joinRoom(action)
|
is RoomDirectoryAction.JoinRoom -> joinRoom(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue