mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 10:55:38 +03:00
thread list loading (#7766)
This commit is contained in:
parent
c74ea2dd16
commit
cf3abd6562
22 changed files with 526 additions and 175 deletions
1
changelog.d/5819.misc
Normal file
1
changelog.d/5819.misc
Normal file
|
@ -0,0 +1 @@
|
|||
[Threads] - added API to fetch threads list from the server instead of building it locally from events
|
|
@ -31,7 +31,6 @@ 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
|
||||
|
@ -119,13 +118,6 @@ class FlowRoom(private val room: Room) {
|
|||
return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
|
||||
}
|
||||
|
||||
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
|
||||
return room.threadsService().getAllThreadSummariesLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
room.threadsService().getAllThreadSummaries()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
|
||||
return room.threadsLocalService().getAllThreadsLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
sealed class FetchThreadsResult {
|
||||
data class ShouldFetchMore(val nextBatch: String) : FetchThreadsResult()
|
||||
object ReachedEnd : FetchThreadsResult()
|
||||
object Failed : FetchThreadsResult()
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ThreadFilter {
|
||||
@Json(name = "all") ALL,
|
||||
@Json(name = "participated") PARTICIPATED,
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.threads
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
|
||||
data class ThreadLivePageResult(
|
||||
val livePagedList: LiveData<PagedList<ThreadSummary>>,
|
||||
val liveBoundaries: LiveData<ResultBoundaries>
|
||||
)
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.room.threads
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
|
||||
/**
|
||||
|
@ -27,15 +27,14 @@ import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
|||
*/
|
||||
interface ThreadsService {
|
||||
|
||||
/**
|
||||
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level.
|
||||
*/
|
||||
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
|
||||
suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult
|
||||
|
||||
suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter = ThreadFilter.ALL): FetchThreadsResult
|
||||
|
||||
/**
|
||||
* Returns a list of all the [ThreadSummary] that exists at the room level.
|
||||
*/
|
||||
fun getAllThreadSummaries(): List<ThreadSummary>
|
||||
suspend fun getAllThreadSummaries(): List<ThreadSummary>
|
||||
|
||||
/**
|
||||
* Enhance the provided ThreadSummary[List] by adding the latest
|
||||
|
@ -51,9 +50,4 @@ interface ThreadsService {
|
|||
* @param limit defines the number of max results the api will respond with
|
||||
*/
|
||||
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
|
||||
|
||||
/**
|
||||
* Fetch all thread summaries for the current room using the enhanced /messages api.
|
||||
*/
|
||||
suspend fun fetchThreadSummaries()
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042
|
|||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||
import javax.inject.Inject
|
||||
|
@ -70,7 +71,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
private val normalizer: Normalizer
|
||||
) : MatrixRealmMigration(
|
||||
dbName = "Session",
|
||||
schemaVersion = 45L,
|
||||
schemaVersion = 46L,
|
||||
) {
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||
|
@ -125,5 +126,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
if (oldVersion < 43) MigrateSessionTo043(realm).perform()
|
||||
if (oldVersion < 44) MigrateSessionTo044(realm).perform()
|
||||
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
||||
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,11 @@ import org.matrix.android.sdk.internal.database.model.EventEntity
|
|||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||
import org.matrix.android.sdk.internal.database.query.get
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
|
@ -113,16 +115,16 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
|||
userId: String,
|
||||
cryptoService: CryptoService? = null,
|
||||
currentTimeMillis: Long,
|
||||
) {
|
||||
): ThreadSummaryEntity? {
|
||||
when (threadSummaryType) {
|
||||
ThreadSummaryUpdateType.REPLACE -> {
|
||||
rootThreadEvent?.eventId ?: return
|
||||
rootThreadEvent.senderId ?: return
|
||||
rootThreadEvent?.eventId ?: return null
|
||||
rootThreadEvent.senderId ?: return null
|
||||
|
||||
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return
|
||||
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return null
|
||||
|
||||
// Something is wrong with the server return
|
||||
if (numberOfThreads <= 0) return
|
||||
if (numberOfThreads <= 0) return null
|
||||
|
||||
val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also {
|
||||
Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
|
||||
|
@ -153,12 +155,13 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
|||
)
|
||||
|
||||
roomEntity.addIfNecessary(threadSummary)
|
||||
return threadSummary
|
||||
}
|
||||
ThreadSummaryUpdateType.ADD -> {
|
||||
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return
|
||||
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return null
|
||||
Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId")
|
||||
|
||||
val threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
|
||||
var threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
|
||||
if (threadSummary != null) {
|
||||
// ThreadSummary exists so lets add the latest event
|
||||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.")
|
||||
|
@ -172,7 +175,7 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
|||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one")
|
||||
threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity ->
|
||||
// Root thread event entity exists so lets create a new record
|
||||
ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).let {
|
||||
threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).also {
|
||||
it.updateThreadSummary(
|
||||
rootThreadEventEntity = rootThreadEventEntity,
|
||||
numberOfThreads = 1,
|
||||
|
@ -183,7 +186,12 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
|||
roomEntity.addIfNecessary(it)
|
||||
}
|
||||
}
|
||||
|
||||
threadSummary?.let {
|
||||
ThreadListPageEntity.get(realm, roomId)?.threadSummaries?.add(it)
|
||||
}
|
||||
}
|
||||
return threadSummary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 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.internal.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
internal class MigrateSessionTo046(realm: DynamicRealm) : RealmMigrator(realm, 46) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.create("ThreadListPageEntity")
|
||||
.addField(ThreadListPageEntityFields.ROOM_ID, String::class.java)
|
||||
.addPrimaryKey(ThreadListPageEntityFields.ROOM_ID)
|
||||
.setRequired(ThreadListPageEntityFields.ROOM_ID, true)
|
||||
.addRealmListField(ThreadListPageEntityFields.THREAD_SUMMARIES.`$`, realm.schema.get("ThreadSummaryEntity")!!)
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model
|
|||
import io.realm.annotations.RealmModule
|
||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
|
||||
/**
|
||||
|
@ -72,6 +73,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||
UserPresenceEntity::class,
|
||||
ThreadSummaryEntity::class,
|
||||
SyncFilterParamsEntity::class,
|
||||
ThreadListPageEntity::class
|
||||
]
|
||||
)
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.internal.database.model.threads
|
||||
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class ThreadListPageEntity(
|
||||
@PrimaryKey var roomId: String = "",
|
||||
var threadSummaries: RealmList<ThreadSummaryEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
companion object
|
||||
}
|
|
@ -40,5 +40,8 @@ internal open class ThreadSummaryEntity(
|
|||
@LinkingObjects("threadSummaries")
|
||||
val room: RealmResults<RoomEntity>? = null
|
||||
|
||||
@LinkingObjects("threadSummaries")
|
||||
val page: RealmResults<ThreadListPageEntity>? = null
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.internal.database.query
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
|
||||
|
||||
internal fun ThreadListPageEntity.Companion.get(realm: Realm, roomId: String): ThreadListPageEntity? {
|
||||
return realm.where<ThreadListPageEntity>().equalTo(ThreadListPageEntityFields.ROOM_ID, roomId).findFirst()
|
||||
}
|
||||
|
||||
internal fun ThreadListPageEntity.Companion.getOrCreate(realm: Realm, roomId: String): ThreadListPageEntity {
|
||||
return get(realm, roomId) ?: realm.createObject(roomId)
|
||||
}
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBod
|
|||
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
|
||||
import org.matrix.android.sdk.internal.session.room.read.ReadBody
|
||||
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse
|
||||
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
|
||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||
import org.matrix.android.sdk.internal.session.room.tags.TagBody
|
||||
|
@ -464,4 +465,12 @@ internal interface RoomAPI {
|
|||
@Path("roomIdOrAlias") roomidOrAlias: String,
|
||||
@Query("via") viaServers: List<String>?
|
||||
): RoomStrippedState
|
||||
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads")
|
||||
suspend fun getThreadsList(
|
||||
@Path("roomId") roomId: String,
|
||||
@Query("include") include: String? = "all",
|
||||
@Query("from") from: String? = null,
|
||||
@Query("limit") limit: Int? = null
|
||||
): ThreadSummariesResponse
|
||||
}
|
||||
|
|
|
@ -16,37 +16,38 @@
|
|||
package org.matrix.android.sdk.internal.session.room.relation.threads
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.RealmList
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.filter.FilterFactory
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/***
|
||||
* This class is responsible to Fetch all the thread in the current room,
|
||||
* To fetch all threads in a room, the /messages API is used with newly added filtering options.
|
||||
*/
|
||||
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, DefaultFetchThreadSummariesTask.Result> {
|
||||
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, FetchThreadsResult> {
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val from: String = "",
|
||||
val limit: Int = 500,
|
||||
val isUserParticipating: Boolean = true
|
||||
val from: String? = null,
|
||||
val limit: Int = 5,
|
||||
val filter: ThreadFilter? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -59,39 +60,43 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
|
|||
private val clock: Clock,
|
||||
) : FetchThreadSummariesTask {
|
||||
|
||||
override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
|
||||
val filter = FilterFactory.createThreadsFilter(
|
||||
numberOfEvents = params.limit,
|
||||
userId = if (params.isUserParticipating) userId else null
|
||||
).toJSONString()
|
||||
|
||||
val response = executeRequest(
|
||||
globalErrorReceiver,
|
||||
canRetry = true
|
||||
) {
|
||||
roomAPI.getRoomMessagesFrom(params.roomId, params.from, PaginationDirection.BACKWARDS.value, params.limit, filter)
|
||||
override suspend fun execute(params: FetchThreadSummariesTask.Params): FetchThreadsResult {
|
||||
val response = executeRequest(globalErrorReceiver) {
|
||||
roomAPI.getThreadsList(
|
||||
roomId = params.roomId,
|
||||
include = params.filter?.toString()?.lowercase(),
|
||||
from = params.from,
|
||||
limit = params.limit
|
||||
)
|
||||
}
|
||||
|
||||
Timber.i("###THREADS DefaultFetchThreadSummariesTask Fetched size:${response.events.size} nextBatch:${response.end} ")
|
||||
handleResponse(response, params)
|
||||
|
||||
return handleResponse(response, params)
|
||||
return when {
|
||||
response.nextBatch != null -> FetchThreadsResult.ShouldFetchMore(response.nextBatch)
|
||||
else -> FetchThreadsResult.ReachedEnd
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleResponse(
|
||||
response: PaginationResponse,
|
||||
response: ThreadSummariesResponse,
|
||||
params: FetchThreadSummariesTask.Params
|
||||
): Result {
|
||||
val rootThreadList = response.events
|
||||
) {
|
||||
val rootThreadList = response.chunk
|
||||
|
||||
val threadSummaries = RealmList<ThreadSummaryEntity>()
|
||||
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction
|
||||
|
||||
val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>()
|
||||
|
||||
for (rootThreadEvent in rootThreadList) {
|
||||
if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
ThreadSummaryEntity.createOrUpdate(
|
||||
val threadSummary = ThreadSummaryEntity.createOrUpdate(
|
||||
threadSummaryType = ThreadSummaryUpdateType.REPLACE,
|
||||
realm = realm,
|
||||
roomId = params.roomId,
|
||||
|
@ -102,14 +107,16 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
|
|||
cryptoService = cryptoService,
|
||||
currentTimeMillis = clock.epochMillis(),
|
||||
)
|
||||
|
||||
threadSummaries.add(threadSummary)
|
||||
}
|
||||
|
||||
val page = ThreadListPageEntity.getOrCreate(realm, params.roomId)
|
||||
threadSummaries.forEach {
|
||||
if (!page.threadSummaries.contains(it)) {
|
||||
page.threadSummaries.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.SUCCESS
|
||||
}
|
||||
|
||||
enum class Result {
|
||||
SHOULD_FETCH_MORE,
|
||||
REACHED_END,
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.internal.session.room.relation.threads
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ThreadSummariesResponse(
|
||||
@Json(name = "chunk") val chunk: List<Event>,
|
||||
@Json(name = "next_batch") val nextBatch: String?,
|
||||
@Json(name = "prev_batch") val prevBatch: String?
|
||||
)
|
|
@ -16,32 +16,39 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.room.threads
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.realm.Realm
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadLivePageResult
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions
|
||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||
import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
|
||||
internal class DefaultThreadsService @AssistedInject constructor(
|
||||
@Assisted private val roomId: String,
|
||||
@UserId private val userId: String,
|
||||
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val threadSummaryMapper: ThreadSummaryMapper
|
||||
private val threadSummaryMapper: ThreadSummaryMapper,
|
||||
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
|
||||
) : ThreadsService {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -49,16 +56,58 @@ internal class DefaultThreadsService @AssistedInject constructor(
|
|||
fun create(roomId: String): DefaultThreadsService
|
||||
}
|
||||
|
||||
override fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{
|
||||
threadSummaryMapper.map(it)
|
||||
override suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
realm.where<ThreadListPageEntity>().findAll().deleteAllFromRealm()
|
||||
}
|
||||
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
realm
|
||||
.where<ThreadSummaryEntity>().equalTo(ThreadSummaryEntityFields.PAGE.ROOM_ID, roomId)
|
||||
.sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING)
|
||||
}
|
||||
|
||||
val dataSourceFactory = realmDataSourceFactory.map {
|
||||
threadSummaryMapper.map(it)
|
||||
}
|
||||
|
||||
val boundaries = MutableLiveData(ResultBoundaries())
|
||||
|
||||
val builder = LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
|
||||
it.setBoundaryCallback(object : PagedList.BoundaryCallback<ThreadSummary>() {
|
||||
override fun onItemAtEndLoaded(itemAtEnd: ThreadSummary) {
|
||||
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
|
||||
}
|
||||
|
||||
override fun onItemAtFrontLoaded(itemAtFront: ThreadSummary) {
|
||||
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
|
||||
}
|
||||
|
||||
override fun onZeroItemsLoaded() {
|
||||
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val livePagedList = monarchy.findAllPagedWithChanges(
|
||||
realmDataSourceFactory,
|
||||
builder
|
||||
)
|
||||
return ThreadLivePageResult(livePagedList, boundaries)
|
||||
}
|
||||
|
||||
override suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter): FetchThreadsResult {
|
||||
return fetchThreadSummariesTask.execute(
|
||||
FetchThreadSummariesTask.Params(
|
||||
roomId = roomId,
|
||||
from = nextBatchId,
|
||||
limit = limit,
|
||||
filter = filter
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAllThreadSummaries(): List<ThreadSummary> {
|
||||
override suspend fun getAllThreadSummaries(): List<ThreadSummary> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ threadSummaryMapper.map(it) }
|
||||
|
@ -81,12 +130,4 @@ internal class DefaultThreadsService @AssistedInject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetchThreadSummaries() {
|
||||
fetchThreadSummariesTask.execute(
|
||||
FetchThreadSummariesTask.Params(
|
||||
roomId = roomId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,24 +19,18 @@ package im.vector.app.features.home.room.threads.list.viewmodel
|
|||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.app.features.home.room.threads.list.model.threadListItem
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
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.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItemOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadListController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val session: Session
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
@ -48,64 +42,7 @@ class ThreadListController @Inject constructor(
|
|||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() =
|
||||
when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) {
|
||||
true -> buildThreadSummaries()
|
||||
false -> buildThreadList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Building thread summaries when homeserver supports threading.
|
||||
*/
|
||||
private fun buildThreadSummaries() {
|
||||
val safeViewState = viewState ?: return
|
||||
val host = this
|
||||
safeViewState.threadSummaryList.invoke()
|
||||
?.filter {
|
||||
if (safeViewState.shouldFilterThreads) {
|
||||
it.isUserParticipating
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
?.forEach { threadSummary ->
|
||||
val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
||||
val lastMessageFormatted = threadSummary.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.latestEvent,
|
||||
latestEdition = it.threadEditions.latestThreadEdition
|
||||
).toString()
|
||||
}
|
||||
val rootMessageFormatted = threadSummary.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.rootEvent,
|
||||
latestEdition = it.threadEditions.rootThreadEdition
|
||||
).toString()
|
||||
}
|
||||
threadListItem {
|
||||
id(threadSummary.rootEvent?.eventId)
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
matrixItem(threadSummary.rootThreadSenderInfo.toMatrixItem())
|
||||
title(threadSummary.rootThreadSenderInfo.displayName.orEmpty())
|
||||
date(date)
|
||||
rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false)
|
||||
// TODO refactor notifications that with the new thread summary
|
||||
threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||
rootMessage(rootMessageFormatted)
|
||||
lastMessage(lastMessageFormatted)
|
||||
lastMessageCounter(threadSummary.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull())
|
||||
itemClickListener {
|
||||
host.listener?.onThreadSummaryClicked(threadSummary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Building local thread list when homeserver do not support threading.
|
||||
*/
|
||||
private fun buildThreadList() {
|
||||
override fun buildModels() {
|
||||
val safeViewState = viewState ?: return
|
||||
val host = this
|
||||
safeViewState.rootThreadEventList.invoke()
|
||||
|
@ -152,7 +89,6 @@ class ThreadListController @Inject constructor(
|
|||
}
|
||||
|
||||
interface Listener {
|
||||
fun onThreadSummaryClicked(threadSummary: ThreadSummary)
|
||||
fun onThreadListClicked(timelineEvent: TimelineEvent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2021 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.app.features.home.room.threads.list.viewmodel
|
||||
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.utils.createUIHandler
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.app.features.home.room.threads.list.model.ThreadListItem_
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItemOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadListPagedController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
) : PagedListEpoxyController<ThreadSummary>(
|
||||
// Important it must match the PageList builder notify Looper
|
||||
modelBuildingHandler = createUIHandler()
|
||||
) {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildItemModel(currentPosition: Int, item: ThreadSummary?): EpoxyModel<*> {
|
||||
if (item == null) {
|
||||
throw java.lang.NullPointerException()
|
||||
}
|
||||
val host = this
|
||||
val date = dateFormatter.format(item.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
||||
val lastMessageFormatted = item.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.latestEvent,
|
||||
latestEdition = it.threadEditions.latestThreadEdition
|
||||
).toString()
|
||||
}
|
||||
val rootMessageFormatted = item.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.rootEvent,
|
||||
latestEdition = it.threadEditions.rootThreadEdition
|
||||
).toString()
|
||||
}
|
||||
|
||||
return ThreadListItem_()
|
||||
.id(item.rootEvent?.eventId)
|
||||
.avatarRenderer(host.avatarRenderer)
|
||||
.matrixItem(item.rootThreadSenderInfo.toMatrixItem())
|
||||
.title(item.rootThreadSenderInfo.displayName.orEmpty())
|
||||
.date(date)
|
||||
.rootMessageDeleted(item.rootEvent?.isRedacted() ?: false)
|
||||
// TODO refactor notifications that with the new thread summary
|
||||
.threadNotificationState(item.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||
.rootMessage(rootMessageFormatted)
|
||||
.lastMessage(lastMessageFormatted)
|
||||
.lastMessageCounter(item.numberOfThreads.toString())
|
||||
.lastMessageMatrixItem(item.latestThreadSenderInfo.toMatrixItemOrNull())
|
||||
.itemClickListener {
|
||||
host.listener?.onThreadSummaryClicked(item)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onThreadSummaryClicked(threadSummary: ThreadSummary)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,11 @@
|
|||
|
||||
package im.vector.app.features.home.room.threads.list.viewmodel
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.paging.PagedList
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
|
@ -29,23 +34,47 @@ import im.vector.app.features.analytics.AnalyticsTracker
|
|||
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
||||
class ThreadListViewModel @AssistedInject constructor(
|
||||
@Assisted val initialState: ThreadListViewState,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val session: Session
|
||||
) :
|
||||
VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
private val session: Session,
|
||||
) : VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)
|
||||
|
||||
private val defaultPagedListConfig = PagedList.Config.Builder()
|
||||
.setPageSize(20)
|
||||
.setInitialLoadSizeHint(40)
|
||||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(10)
|
||||
.build()
|
||||
|
||||
private var nextBatchId: String? = null
|
||||
private var hasReachedEnd: Boolean = false
|
||||
private var boundariesJob: Job? = null
|
||||
|
||||
private var livePagedList: LiveData<PagedList<ThreadSummary>>? = null
|
||||
private val _threadsLivePagedList = MutableLiveData<PagedList<ThreadSummary>>()
|
||||
val threadsLivePagedList: LiveData<PagedList<ThreadSummary>> = _threadsLivePagedList
|
||||
private val internalPagedListObserver = Observer<PagedList<ThreadSummary>> {
|
||||
_threadsLivePagedList.postValue(it)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(initialState: ThreadListViewState): ThreadListViewModel
|
||||
|
@ -54,7 +83,7 @@ class ThreadListViewModel @AssistedInject constructor(
|
|||
companion object : MavericksViewModelFactory<ThreadListViewModel, ThreadListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: ThreadListViewState): ThreadListViewModel? {
|
||||
override fun create(viewModelContext: ViewModelContext, state: ThreadListViewState): ThreadListViewModel {
|
||||
val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
return fragment.threadListViewModelFactory.create(state)
|
||||
}
|
||||
|
@ -72,7 +101,7 @@ class ThreadListViewModel @AssistedInject constructor(
|
|||
private fun fetchAndObserveThreads() {
|
||||
when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) {
|
||||
true -> {
|
||||
fetchThreadList()
|
||||
setLoading(true)
|
||||
observeThreadSummaries()
|
||||
}
|
||||
false -> observeThreadsList()
|
||||
|
@ -82,14 +111,33 @@ class ThreadListViewModel @AssistedInject constructor(
|
|||
/**
|
||||
* Observing thread summaries when homeserver support threading.
|
||||
*/
|
||||
private fun observeThreadSummaries() {
|
||||
room?.flow()
|
||||
?.liveThreadSummaries()
|
||||
?.map { room.threadsService().enhanceThreadWithEditions(it) }
|
||||
?.flowOn(room.coroutineDispatchers.io)
|
||||
?.execute { asyncThreads ->
|
||||
copy(threadSummaryList = asyncThreads)
|
||||
}
|
||||
private fun observeThreadSummaries() = withState { state ->
|
||||
viewModelScope.launch {
|
||||
nextBatchId = null
|
||||
hasReachedEnd = false
|
||||
|
||||
livePagedList?.removeObserver(internalPagedListObserver)
|
||||
|
||||
room?.threadsService()
|
||||
?.getPagedThreadsList(state.shouldFilterThreads, defaultPagedListConfig)?.let { result ->
|
||||
livePagedList = result.livePagedList
|
||||
|
||||
livePagedList?.observeForever(internalPagedListObserver)
|
||||
|
||||
boundariesJob = result.liveBoundaries.asFlow()
|
||||
.onEach {
|
||||
if (it.endLoaded) {
|
||||
if (!hasReachedEnd) {
|
||||
fetchNextPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
fetchNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,14 +159,6 @@ class ThreadListViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchThreadList() {
|
||||
viewModelScope.launch {
|
||||
setLoading(true)
|
||||
room?.threadsService()?.fetchThreadSummaries()
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
setState {
|
||||
copy(isLoading = isLoading)
|
||||
|
@ -132,5 +172,30 @@ class ThreadListViewModel @AssistedInject constructor(
|
|||
setState {
|
||||
copy(shouldFilterThreads = shouldFilterThreads)
|
||||
}
|
||||
|
||||
fetchAndObserveThreads()
|
||||
}
|
||||
|
||||
private suspend fun fetchNextPage() {
|
||||
val filter = when (awaitState().shouldFilterThreads) {
|
||||
true -> ThreadFilter.PARTICIPATED
|
||||
false -> ThreadFilter.ALL
|
||||
}
|
||||
room?.threadsService()?.fetchThreadList(
|
||||
nextBatchId = nextBatchId,
|
||||
limit = defaultPagedListConfig.pageSize,
|
||||
filter = filter,
|
||||
).let { result ->
|
||||
when (result) {
|
||||
is FetchThreadsResult.ReachedEnd -> {
|
||||
hasReachedEnd = true
|
||||
}
|
||||
is FetchThreadsResult.ShouldFetchMore -> {
|
||||
nextBatchId = result.nextBatch
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,9 @@ import com.airbnb.mvrx.Async
|
|||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||
|
||||
data class ThreadListViewState(
|
||||
val threadSummaryList: Async<List<ThreadSummary>> = Uninitialized,
|
||||
val rootThreadEventList: Async<List<ThreadTimelineEvent>> = Uninitialized,
|
||||
val shouldFilterThreads: Boolean = false,
|
||||
val isLoading: Boolean = false,
|
||||
|
|
|
@ -40,6 +40,7 @@ import im.vector.app.features.home.room.threads.ThreadsActivity
|
|||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListPagedController
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
|
||||
import im.vector.app.features.rageshake.BugReporter
|
||||
|
@ -52,12 +53,14 @@ import javax.inject.Inject
|
|||
@AndroidEntryPoint
|
||||
class ThreadListFragment :
|
||||
VectorBaseFragment<FragmentThreadListBinding>(),
|
||||
ThreadListPagedController.Listener,
|
||||
ThreadListController.Listener,
|
||||
VectorMenuProvider {
|
||||
|
||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||
@Inject lateinit var bugReporter: BugReporter
|
||||
@Inject lateinit var threadListController: ThreadListController
|
||||
@Inject lateinit var threadListController: ThreadListPagedController
|
||||
@Inject lateinit var legacyThreadListController: ThreadListController
|
||||
@Inject lateinit var threadListViewModelFactory: ThreadListViewModel.Factory
|
||||
|
||||
private val threadListViewModel: ThreadListViewModel by fragmentViewModel()
|
||||
|
@ -100,7 +103,7 @@ class ThreadListFragment :
|
|||
val filterBadge = filterIcon.findViewById<View>(R.id.threadListFilterBadge)
|
||||
filterBadge.isVisible = state.shouldFilterThreads
|
||||
when (threadListViewModel.canHomeserverUseThreading()) {
|
||||
true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty()
|
||||
true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = true
|
||||
false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +114,18 @@ class ThreadListFragment :
|
|||
initToolbar()
|
||||
initTextConstants()
|
||||
initBetaFeedback()
|
||||
views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false)
|
||||
threadListController.listener = this
|
||||
|
||||
if (threadListViewModel.canHomeserverUseThreading()) {
|
||||
views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false)
|
||||
threadListController.listener = this
|
||||
|
||||
threadListViewModel.threadsLivePagedList.observe(viewLifecycleOwner) { threadsList ->
|
||||
threadListController.submitList(threadsList)
|
||||
}
|
||||
} else {
|
||||
views.threadListRecyclerView.configureWith(legacyThreadListController, TimelineItemAnimator(), hasFixedSize = false)
|
||||
legacyThreadListController.listener = this
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -144,7 +157,9 @@ class ThreadListFragment :
|
|||
override fun invalidate() = withState(threadListViewModel) { state ->
|
||||
invalidateOptionsMenu()
|
||||
renderEmptyStateIfNeeded(state)
|
||||
threadListController.update(state)
|
||||
if (!threadListViewModel.canHomeserverUseThreading()) {
|
||||
legacyThreadListController.update(state)
|
||||
}
|
||||
renderLoaderIfNeeded(state)
|
||||
}
|
||||
|
||||
|
@ -185,7 +200,7 @@ class ThreadListFragment :
|
|||
|
||||
private fun renderEmptyStateIfNeeded(state: ThreadListViewState) {
|
||||
when (threadListViewModel.canHomeserverUseThreading()) {
|
||||
true -> views.threadListEmptyConstraintLayout.isVisible = state.threadSummaryList.invoke().isNullOrEmpty()
|
||||
true -> views.threadListEmptyConstraintLayout.isVisible = false
|
||||
false -> views.threadListEmptyConstraintLayout.isVisible = state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue