Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live Threads #5298

Merged
merged 47 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
49b7726
- Integrate /relations API to create a live thread timeline
ariskotsomitopoulos Feb 14, 2022
83d937b
format ktlint
ariskotsomitopoulos Feb 14, 2022
f1b11df
Merge branch 'develop' into feature/aris/threads_live_timeline
ariskotsomitopoulos Feb 14, 2022
27bc43c
Fix realm migration
ariskotsomitopoulos Feb 14, 2022
f98b595
Merge branch 'develop' into feature/aris/threads_live_timeline
ariskotsomitopoulos Feb 14, 2022
e9e5d68
Fix realm migration from 25 to 26
ariskotsomitopoulos Feb 14, 2022
830c38f
format ktlint
ariskotsomitopoulos Feb 14, 2022
83088bb
Introduce live thread summaries using the enhanced /messages API from…
ariskotsomitopoulos Feb 18, 2022
f4f48b9
Improve home server capabilities for threads
ariskotsomitopoulos Feb 21, 2022
2b740a1
Implement permalink support for /relations live thread timeline
ariskotsomitopoulos Feb 21, 2022
0f721d9
Ktlint format
ariskotsomitopoulos Feb 21, 2022
79a231f
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Feb 22, 2022
deb86d2
Resolve real migration conflicts
ariskotsomitopoulos Feb 22, 2022
9953d0d
Resolve realm migration conflicts
ariskotsomitopoulos Feb 22, 2022
2054c57
Fix quality check errors
ariskotsomitopoulos Feb 22, 2022
f7f363c
Fix wrong copyrights
ariskotsomitopoulos Feb 22, 2022
79c97ac
Formating code
ariskotsomitopoulos Feb 22, 2022
a9b3882
Add changelogs
ariskotsomitopoulos Feb 23, 2022
8788fb9
Add new use case about threads in the allScreensTest
ariskotsomitopoulos Feb 23, 2022
8b25421
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Feb 24, 2022
eda723c
Remove fetching thread summaries when homeserver do not support MSC3440
ariskotsomitopoulos Feb 28, 2022
214e0ef
Add Markdown support to thread summaries and thread list
ariskotsomitopoulos Mar 2, 2022
33b1700
force refresh home server capabilities
ariskotsomitopoulos Mar 3, 2022
719e254
Format Code
ariskotsomitopoulos Mar 3, 2022
e4282e5
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Mar 3, 2022
39bd437
Temp fix Realm crash
ariskotsomitopoulos Mar 3, 2022
daafddb
fix Realm crash
ariskotsomitopoulos Mar 3, 2022
bce5bc8
Fix wrong versioning regex pattern
ariskotsomitopoulos Mar 5, 2022
d19dd91
Format code
ariskotsomitopoulos Mar 5, 2022
557fd7e
Replace thread timeline and thread summaries EventInsertType from INC…
ariskotsomitopoulos Mar 8, 2022
8c6902a
Fix reply within thread edition
ariskotsomitopoulos Mar 8, 2022
a53d5bd
Remove eventType from /relations api for threads
ariskotsomitopoulos Mar 8, 2022
03f293f
Remove io.element.thread and add stable m.thread prefix
ariskotsomitopoulos Mar 10, 2022
45ee9f8
Check if the server supports MSC3440 using the stable flag from /vers…
ariskotsomitopoulos Mar 10, 2022
fd30d38
Fix line length
ariskotsomitopoulos Mar 10, 2022
2111192
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Mar 10, 2022
a758ad7
Add is_falling_back support for rich thread replies
ariskotsomitopoulos Mar 10, 2022
34cfdfb
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Mar 10, 2022
f31b130
Fix unit tests
ariskotsomitopoulos Mar 10, 2022
c2ec7cf
Add more clear documentation
ariskotsomitopoulos Mar 14, 2022
d215f03
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Mar 14, 2022
fef36d9
Temporarily fix develop crash
ariskotsomitopoulos Mar 14, 2022
d894d85
Format text
ariskotsomitopoulos Mar 14, 2022
d7c486c
Add fallback support rendering proposed in MSC3676
ariskotsomitopoulos Mar 14, 2022
c9f0706
Revert temporary fix for develop crash
ariskotsomitopoulos Mar 15, 2022
8a862d0
Merge branch 'develop' into feature/aris/thread_live_thread_list
ariskotsomitopoulos Mar 15, 2022
4d76c0d
Fix build error
ariskotsomitopoulos Mar 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5230.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Thread timeline is now live and much faster especially for large or old threads
1 change: 1 addition & 0 deletions changelog.d/5232.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
View all threads per room screen is now live when the home server supports threads
1 change: 1 addition & 0 deletions changelog.d/5271.sdk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds support for MSC3440, additional threads homeserver capabilities
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.session.room.send.UserDraft
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
Expand Down Expand Up @@ -101,13 +102,18 @@ class FlowRoom(private val room: Room) {
return room.getLiveRoomNotificationState().asFlow()
}

fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
return room.getAllThreadSummariesLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreadSummaries()
}
}
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
return room.getAllThreadsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreads()
}
}

fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
return room.getMarkedThreadNotificationsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.timelineEvents.size shouldBeEqualTo 1
}
}
Expand All @@ -74,8 +78,16 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.addTimelineEvent(
roomId = ROOM_ID,
eventEntity = fakeEvent,
direction = PaginationDirection.FORWARDS,
roomMemberContentsByUser = emptyMap())
chunk.timelineEvents.size shouldBeEqualTo 1
}
}
Expand Down Expand Up @@ -144,7 +156,11 @@ internal class ChunkEntityTest : InstrumentedTest {
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
realm.copyToRealm(it)
}
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
addTimelineEvent(
roomId = roomId,
eventEntity = fakeEvent,
direction = direction,
roomMemberContentsByUser = emptyMap())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AggregatedRelations(
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null,
@Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,11 @@ data class Event(
*/
fun getDecryptedTextSummary(): String? {
if (isRedacted()) return "Message Deleted"
val text = getDecryptedValue() ?: return null
val text = getDecryptedValue() ?: run {
if (isPoll()) { return getPollQuestion() ?: "created a poll." }
return null
}

return when {
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
isFileMessage() -> "sent a file."
Expand Down Expand Up @@ -385,12 +389,12 @@ fun Event.isReply(): Boolean {
}

fun Event.isReplyRenderedInThread(): Boolean {
return isReply() && getRelationContent()?.inReplyTo?.shouldRenderInThread() == true
return isReply() && getRelationContent()?.shouldRenderInThread() == true
}

fun Event.isThread(): Boolean = getRelationContentForType(RelationType.IO_THREAD)?.eventId != null
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null

fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.IO_THREAD)?.eventId
fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.THREAD)?.eventId

fun Event.isEdition(): Boolean {
return getRelationContentForType(RelationType.REPLACE)?.eventId != null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.events.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class LatestThreadUnsignedRelation(
override val limited: Boolean? = false,
override val count: Int? = 0,
@Json(name = "latest_event")
val event: Event? = null,
@Json(name = "current_user_participated")
val isUserParticipating: Boolean? = false

) : UnsignedRelationInfo
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ object RelationType {

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

/** Lets you define an event which adds a response to an existing event.*/
const val RESPONSE = "org.matrix.response"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ data class HomeServerCapabilities(
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
val roomVersions: RoomVersionCapabilities? = null
val roomVersions: RoomVersionCapabilities? = null,
/**
* True if the home server support threading
*/
var canUseThreading: Boolean = false
) {

enum class RoomCapabilitySupport {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
Expand All @@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional
interface Room :
TimelineService,
ThreadsService,
ThreadsLocalService,
SendService,
DraftService,
ReadService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ data class ReactionInfo(
@Json(name = "key") val key: String,
// always null for reaction
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
@Json(name = "option") override val option: Int? = null
@Json(name = "option") override val option: Int? = null,
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
) : RelationContent
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ interface RelationContent {
val eventId: String?
val inReplyTo: ReplyToContent?
val option: Int?

/**
* This flag indicates that the message should be rendered as a reply
* fallback, when isFallingBack = false
*/
val isFallingBack: Boolean?
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ data class RelationDefaultContent(
@Json(name = "rel_type") override val type: String?,
@Json(name = "event_id") override val eventId: String?,
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
@Json(name = "option") override val option: Int? = null
@Json(name = "option") override val option: Int? = null,
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
) : RelationContent

fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,4 @@ interface RelationService {
autoMarkdown: Boolean = false,
formattedText: String? = null,
eventReplied: TimelineEvent? = null): Cancelable?

/**
* Get all the thread replies for the specified rootThreadEventId
* The return list will contain the original root thread event and all the thread replies to that event
* Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready
* from the backend
* @param rootThreadEventId the root thread eventId
*/
suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,5 @@ import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ReplyToContent(
@Json(name = "event_id") val eventId: String? = null,
@Json(name = "render_in") val renderIn: List<String>? = null
@Json(name = "event_id") val eventId: String? = null
)

fun ReplyToContent.shouldRenderInThread(): Boolean = renderIn?.contains("m.thread") == true
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,43 @@
package org.matrix.android.sdk.api.session.room.threads

import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary

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

/**
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level
*/
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>

/**
* Returns a list of all the thread root TimelineEvents that exists at the room level
* Returns a list of all the [ThreadSummary] that exists at the room level
*/
fun getAllThreads(): List<TimelineEvent>
fun getAllThreadSummaries(): List<ThreadSummary>

/**
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>

/**
* Returns a list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotifications(): List<TimelineEvent>

/**
* Returns whether or not the current user is participating in the thread
* @param rootThreadEventId the eventId of the current thread
* Enhance the provided ThreadSummary[List] by adding the latest
* message edition for that thread
* @return the enhanced [List] with edited updates
*/
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
fun enhanceThreadWithEditions(threads: List<ThreadSummary>): List<ThreadSummary>

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

/**
* Marks the current thread as read in local DB.
* note: read receipts within threads are not yet supported with the API
* @param rootThreadEventId the root eventId of the current thread
* Fetch all thread summaries for the current room using the enhanced /messages api
*/
suspend fun markThreadAsRead(rootThreadEventId: String)
suspend fun fetchThreadSummaries()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.session.room.threads.local

import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent

/**
* This interface defines methods to interact with thread related features.
* It's the local threads implementation and assumes that the homeserver
* do not support threads
*/
interface ThreadsLocalService {

/**
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
*/
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>

/**
* Returns a list of all the thread root TimelineEvents that exists at the room level
*/
fun getAllThreads(): List<TimelineEvent>

/**
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>

/**
* Returns a list of all the marked unread threads that exists at the room level
*/
fun getMarkedThreadNotifications(): List<TimelineEvent>

/**
* Returns whether or not the current user is participating in the thread
* @param rootThreadEventId the eventId of the current thread
*/
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean

/**
* Enhance the provided root thread TimelineEvent [List] by adding the latest
* message edition for that thread
* @return the enhanced [List] with edited updates
*/
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>

/**
* Marks the current thread as read in local DB.
* note: read receipts within threads are not yet supported with the API
* @param rootThreadEventId the root eventId of the current thread
*/
suspend fun markThreadAsRead(rootThreadEventId: String)
}
Loading