Skip to content

Commit e0b93c2

Browse files
Merge pull request #5298 from vector-im/feature/aris/thread_live_thread_list
Live Threads
2 parents 30c325b + 4d76c0d commit e0b93c2

File tree

88 files changed

+2048
-321
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+2048
-321
lines changed

changelog.d/5230.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Thread timeline is now live and much faster especially for large or old threads

changelog.d/5232.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
View all threads per room screen is now live when the home server supports threads

changelog.d/5271.sdk

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adds support for MSC3440, additional threads homeserver capabilities

matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
2828
import org.matrix.android.sdk.api.session.room.model.RoomSummary
2929
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
3030
import org.matrix.android.sdk.api.session.room.send.UserDraft
31+
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
3132
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
3233
import org.matrix.android.sdk.api.util.Optional
3334
import org.matrix.android.sdk.api.util.toOptional
@@ -101,13 +102,18 @@ class FlowRoom(private val room: Room) {
101102
return room.getLiveRoomNotificationState().asFlow()
102103
}
103104

105+
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
106+
return room.getAllThreadSummariesLive().asFlow()
107+
.startWith(room.coroutineDispatchers.io) {
108+
room.getAllThreadSummaries()
109+
}
110+
}
104111
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
105112
return room.getAllThreadsLive().asFlow()
106113
.startWith(room.coroutineDispatchers.io) {
107114
room.getAllThreads()
108115
}
109116
}
110-
111117
fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
112118
return room.getMarkedThreadNotificationsLive().asFlow()
113119
.startWith(room.coroutineDispatchers.io) {

matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt

+20-4
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ internal class ChunkEntityTest : InstrumentedTest {
6262
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
6363
realm.copyToRealm(it)
6464
}
65-
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
65+
chunk.addTimelineEvent(
66+
roomId = ROOM_ID,
67+
eventEntity = fakeEvent,
68+
direction = PaginationDirection.FORWARDS,
69+
roomMemberContentsByUser = emptyMap())
6670
chunk.timelineEvents.size shouldBeEqualTo 1
6771
}
6872
}
@@ -74,8 +78,16 @@ internal class ChunkEntityTest : InstrumentedTest {
7478
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
7579
realm.copyToRealm(it)
7680
}
77-
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
78-
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
81+
chunk.addTimelineEvent(
82+
roomId = ROOM_ID,
83+
eventEntity = fakeEvent,
84+
direction = PaginationDirection.FORWARDS,
85+
roomMemberContentsByUser = emptyMap())
86+
chunk.addTimelineEvent(
87+
roomId = ROOM_ID,
88+
eventEntity = fakeEvent,
89+
direction = PaginationDirection.FORWARDS,
90+
roomMemberContentsByUser = emptyMap())
7991
chunk.timelineEvents.size shouldBeEqualTo 1
8092
}
8193
}
@@ -144,7 +156,11 @@ internal class ChunkEntityTest : InstrumentedTest {
144156
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
145157
realm.copyToRealm(it)
146158
}
147-
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
159+
addTimelineEvent(
160+
roomId = roomId,
161+
eventEntity = fakeEvent,
162+
direction = direction,
163+
roomMemberContentsByUser = emptyMap())
148164
}
149165
}
150166

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,6 @@ import com.squareup.moshi.JsonClass
4949
@JsonClass(generateAdapter = true)
5050
data class AggregatedRelations(
5151
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
52-
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null
52+
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null,
53+
@Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null
5354
)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt

+8-4
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,11 @@ data class Event(
201201
*/
202202
fun getDecryptedTextSummary(): String? {
203203
if (isRedacted()) return "Message Deleted"
204-
val text = getDecryptedValue() ?: return null
204+
val text = getDecryptedValue() ?: run {
205+
if (isPoll()) { return getPollQuestion() ?: "created a poll." }
206+
return null
207+
}
208+
205209
return when {
206210
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
207211
isFileMessage() -> "sent a file."
@@ -385,12 +389,12 @@ fun Event.isReply(): Boolean {
385389
}
386390

387391
fun Event.isReplyRenderedInThread(): Boolean {
388-
return isReply() && getRelationContent()?.inReplyTo?.shouldRenderInThread() == true
392+
return isReply() && getRelationContent()?.shouldRenderInThread() == true
389393
}
390394

391-
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.IO_THREAD)?.eventId != null
395+
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null
392396

393-
fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.IO_THREAD)?.eventId
397+
fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.THREAD)?.eventId
394398

395399
fun Event.isEdition(): Boolean {
396400
return getRelationContentForType(RelationType.REPLACE)?.eventId != null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.matrix.android.sdk.api.session.events.model
17+
18+
import com.squareup.moshi.Json
19+
import com.squareup.moshi.JsonClass
20+
21+
@JsonClass(generateAdapter = true)
22+
data class LatestThreadUnsignedRelation(
23+
override val limited: Boolean? = false,
24+
override val count: Int? = 0,
25+
@Json(name = "latest_event")
26+
val event: Event? = null,
27+
@Json(name = "current_user_participated")
28+
val isUserParticipating: Boolean? = false
29+
30+
) : UnsignedRelationInfo

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ object RelationType {
3030

3131
/** Lets you define an event which is a thread reply to an existing event.*/
3232
const val THREAD = "m.thread"
33-
const val IO_THREAD = "io.element.thread"
3433

3534
/** Lets you define an event which adds a response to an existing event.*/
3635
const val RESPONSE = "org.matrix.response"

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ data class HomeServerCapabilities(
5050
* This capability describes the default and available room versions a server supports, and at what level of stability.
5151
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
5252
*/
53-
val roomVersions: RoomVersionCapabilities? = null
53+
val roomVersions: RoomVersionCapabilities? = null,
54+
/**
55+
* True if the home server support threading
56+
*/
57+
var canUseThreading: Boolean = false
5458
) {
5559

5660
enum class RoomCapabilitySupport {

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService
3333
import org.matrix.android.sdk.api.session.room.state.StateService
3434
import org.matrix.android.sdk.api.session.room.tags.TagsService
3535
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
36+
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
3637
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
3738
import org.matrix.android.sdk.api.session.room.typing.TypingService
3839
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
@@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional
4748
interface Room :
4849
TimelineService,
4950
ThreadsService,
51+
ThreadsLocalService,
5052
SendService,
5153
DraftService,
5254
ReadService,

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/ReactionInfo.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ data class ReactionInfo(
2626
@Json(name = "key") val key: String,
2727
// always null for reaction
2828
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
29-
@Json(name = "option") override val option: Int? = null
29+
@Json(name = "option") override val option: Int? = null,
30+
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
3031
) : RelationContent

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationContent.kt

+6
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ interface RelationContent {
2424
val eventId: String?
2525
val inReplyTo: ReplyToContent?
2626
val option: Int?
27+
28+
/**
29+
* This flag indicates that the message should be rendered as a reply
30+
* fallback, when isFallingBack = false
31+
*/
32+
val isFallingBack: Boolean?
2733
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationDefaultContent.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ data class RelationDefaultContent(
2323
@Json(name = "rel_type") override val type: String?,
2424
@Json(name = "event_id") override val eventId: String?,
2525
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
26-
@Json(name = "option") override val option: Int? = null
26+
@Json(name = "option") override val option: Int? = null,
27+
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
2728
) : RelationContent
29+
30+
fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt

-9
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,4 @@ interface RelationService {
163163
autoMarkdown: Boolean = false,
164164
formattedText: String? = null,
165165
eventReplied: TimelineEvent? = null): Cancelable?
166-
167-
/**
168-
* Get all the thread replies for the specified rootThreadEventId
169-
* The return list will contain the original root thread event and all the thread replies to that event
170-
* Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready
171-
* from the backend
172-
* @param rootThreadEventId the root thread eventId
173-
*/
174-
suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean
175166
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/ReplyToContent.kt

+1-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,5 @@ import com.squareup.moshi.JsonClass
2121

2222
@JsonClass(generateAdapter = true)
2323
data class ReplyToContent(
24-
@Json(name = "event_id") val eventId: String? = null,
25-
@Json(name = "render_in") val renderIn: List<String>? = null
24+
@Json(name = "event_id") val eventId: String? = null
2625
)
27-
28-
fun ReplyToContent.shouldRenderInThread(): Boolean = renderIn?.contains("m.thread") == true

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/ThreadsService.kt

+20-28
Original file line numberDiff line numberDiff line change
@@ -17,51 +17,43 @@
1717
package org.matrix.android.sdk.api.session.room.threads
1818

1919
import androidx.lifecycle.LiveData
20-
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
20+
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
2121

2222
/**
23-
* This interface defines methods to interact with threads related features.
24-
* It's implemented at the room level within the main timeline.
23+
* This interface defines methods to interact with thread related features.
24+
* It's the dynamic threads implementation and the homeserver must return
25+
* a capability entry for threads. If the server do not support m.thread
26+
* then [ThreadsLocalService] should be used instead
2527
*/
2628
interface ThreadsService {
2729

2830
/**
29-
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
31+
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level
3032
*/
31-
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
33+
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
3234

3335
/**
34-
* Returns a list of all the thread root TimelineEvents that exists at the room level
36+
* Returns a list of all the [ThreadSummary] that exists at the room level
3537
*/
36-
fun getAllThreads(): List<TimelineEvent>
38+
fun getAllThreadSummaries(): List<ThreadSummary>
3739

3840
/**
39-
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
40-
*/
41-
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
42-
43-
/**
44-
* Returns a list of all the marked unread threads that exists at the room level
45-
*/
46-
fun getMarkedThreadNotifications(): List<TimelineEvent>
47-
48-
/**
49-
* Returns whether or not the current user is participating in the thread
50-
* @param rootThreadEventId the eventId of the current thread
41+
* Enhance the provided ThreadSummary[List] by adding the latest
42+
* message edition for that thread
43+
* @return the enhanced [List] with edited updates
5144
*/
52-
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
45+
fun enhanceThreadWithEditions(threads: List<ThreadSummary>): List<ThreadSummary>
5346

5447
/**
55-
* Enhance the provided root thread TimelineEvent [List] by adding the latest
56-
* message edition for that thread
57-
* @return the enhanced [List] with edited updates
48+
* Fetch all thread replies for the specified thread using the /relations api
49+
* @param rootThreadEventId the root thread eventId
50+
* @param from defines the token that will fetch from that position
51+
* @param limit defines the number of max results the api will respond with
5852
*/
59-
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
53+
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
6054

6155
/**
62-
* Marks the current thread as read in local DB.
63-
* note: read receipts within threads are not yet supported with the API
64-
* @param rootThreadEventId the root eventId of the current thread
56+
* Fetch all thread summaries for the current room using the enhanced /messages api
6557
*/
66-
suspend fun markThreadAsRead(rootThreadEventId: String)
58+
suspend fun fetchThreadSummaries()
6759
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.api.session.room.threads.local
18+
19+
import androidx.lifecycle.LiveData
20+
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
21+
22+
/**
23+
* This interface defines methods to interact with thread related features.
24+
* It's the local threads implementation and assumes that the homeserver
25+
* do not support threads
26+
*/
27+
interface ThreadsLocalService {
28+
29+
/**
30+
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
31+
*/
32+
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
33+
34+
/**
35+
* Returns a list of all the thread root TimelineEvents that exists at the room level
36+
*/
37+
fun getAllThreads(): List<TimelineEvent>
38+
39+
/**
40+
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
41+
*/
42+
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
43+
44+
/**
45+
* Returns a list of all the marked unread threads that exists at the room level
46+
*/
47+
fun getMarkedThreadNotifications(): List<TimelineEvent>
48+
49+
/**
50+
* Returns whether or not the current user is participating in the thread
51+
* @param rootThreadEventId the eventId of the current thread
52+
*/
53+
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
54+
55+
/**
56+
* Enhance the provided root thread TimelineEvent [List] by adding the latest
57+
* message edition for that thread
58+
* @return the enhanced [List] with edited updates
59+
*/
60+
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
61+
62+
/**
63+
* Marks the current thread as read in local DB.
64+
* note: read receipts within threads are not yet supported with the API
65+
* @param rootThreadEventId the root eventId of the current thread
66+
*/
67+
suspend fun markThreadAsRead(rootThreadEventId: String)
68+
}

0 commit comments

Comments
 (0)