mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge pull request #6413 from vector-im/feature/bma/room_member_loading
Show a loader if all the Room Member are not yet loaded.
This commit is contained in:
commit
58580f1e6a
12 changed files with 155 additions and 22 deletions
1
changelog.d/6413.feature
Normal file
1
changelog.d/6413.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Show a loader if all the Room Members are not yet loaded.
|
|
@ -53,6 +53,13 @@ class FlowRoom(private val room: Room) {
|
|||
}
|
||||
}
|
||||
|
||||
fun liveAreAllMembersLoaded(): Flow<Boolean> {
|
||||
return room.membershipService().areAllMembersLoadedLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
room.membershipService().areAllMembersLoaded()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveAnnotationSummary(eventId: String): Flow<Optional<EventAnnotationsSummary>> {
|
||||
return room.relationService().getEventAnnotationsSummaryLive(eventId).asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
|
|
|
@ -30,6 +30,20 @@ interface MembershipService {
|
|||
*/
|
||||
suspend fun loadRoomMembersIfNeeded()
|
||||
|
||||
/**
|
||||
* All the room members can be not loaded, for instance after an initial sync.
|
||||
* All the members will be loaded when calling [loadRoomMembersIfNeeded], or when sending an encrypted
|
||||
* event to the room.
|
||||
* The fun let the app know if all the members have been loaded for this room.
|
||||
* @return true if all the members are loaded, or false elsewhere.
|
||||
*/
|
||||
suspend fun areAllMembersLoaded(): Boolean
|
||||
|
||||
/**
|
||||
* Live version for [areAllMembersLoaded].
|
||||
*/
|
||||
fun areAllMembersLoadedLive(): LiveData<Boolean>
|
||||
|
||||
/**
|
||||
* Return the roomMember with userId or null.
|
||||
* @param userId the userId param to look for
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomDataSource @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
) {
|
||||
fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {
|
||||
var result: RoomMembersLoadStatusType?
|
||||
Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus
|
||||
}
|
||||
return result ?: RoomMembersLoadStatusType.NONE
|
||||
}
|
||||
|
||||
fun getRoomMembersLoadStatusLive(roomId: String): LiveData<Boolean> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{
|
||||
RoomEntity.where(it, roomId)
|
||||
},
|
||||
{
|
||||
it.membersLoadStatus == RoomMembersLoadStatusType.LOADED
|
||||
}
|
||||
)
|
||||
|
||||
return Transformations.map(liveData) { results ->
|
||||
results.firstOrNull().orFalse()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,10 +31,12 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.session.room.RoomDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask
|
||||
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask
|
||||
import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask
|
||||
|
@ -47,6 +49,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
private val inviteTask: InviteTask,
|
||||
private val inviteThreePidTask: InviteThreePidTask,
|
||||
private val membershipAdminTask: MembershipAdminTask,
|
||||
private val roomDataSource: RoomDataSource,
|
||||
@UserId
|
||||
private val userId: String,
|
||||
private val queryStringValueProcessor: QueryStringValueProcessor
|
||||
|
@ -62,6 +65,15 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
|||
loadRoomMembersTask.execute(params)
|
||||
}
|
||||
|
||||
override suspend fun areAllMembersLoaded(): Boolean {
|
||||
val status = roomDataSource.getRoomMembersLoadStatus(roomId)
|
||||
return status == RoomMembersLoadStatusType.LOADED
|
||||
}
|
||||
|
||||
override fun areAllMembersLoadedLive(): LiveData<Boolean> {
|
||||
return roomDataSource.getRoomMembersLoadStatusLive(roomId)
|
||||
}
|
||||
|
||||
override fun getRoomMember(userId: String): RoomMemberSummary? {
|
||||
val roomMemberEntity = monarchy.fetchCopied {
|
||||
RoomMemberHelper(it, roomId).getLastRoomMember(userId)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.matrix.android.sdk.internal.session.room.membership
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
@ -38,6 +37,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
|||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.RoomDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
|
@ -58,6 +58,7 @@ internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit>
|
|||
internal class DefaultLoadRoomMembersTask @Inject constructor(
|
||||
private val roomAPI: RoomAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val roomDataSource: RoomDataSource,
|
||||
private val syncTokenStore: SyncTokenStore,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||
|
@ -68,7 +69,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
|
|||
) : LoadRoomMembersTask {
|
||||
|
||||
override suspend fun execute(params: LoadRoomMembersTask.Params) {
|
||||
when (getRoomMembersLoadStatus(params.roomId)) {
|
||||
when (roomDataSource.getRoomMembersLoadStatus(params.roomId)) {
|
||||
RoomMembersLoadStatusType.NONE -> doRequest(params)
|
||||
RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params)
|
||||
RoomMembersLoadStatusType.LOADED -> Unit
|
||||
|
@ -142,14 +143,6 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {
|
||||
var result: RoomMembersLoadStatusType?
|
||||
Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus
|
||||
}
|
||||
return result ?: RoomMembersLoadStatusType.NONE
|
||||
}
|
||||
|
||||
private suspend fun setRoomMembersLoadStatus(roomId: String, status: RoomMembersLoadStatusType) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.mvrx.args
|
||||
|
@ -114,6 +115,7 @@ class RoomMemberListFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { viewState ->
|
||||
views.roomSettingGeneric.progressBar.isGone = viewState.areAllMembersLoaded
|
||||
roomMemberListController.setData(viewState)
|
||||
renderRoomSummary(viewState)
|
||||
views.inviteUsersButton.isVisible = viewState.actionsPermissions.canInvite
|
||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorViewModel
|
|||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -66,6 +67,7 @@ class RoomMemberListViewModel @AssistedInject constructor(
|
|||
companion object : MavericksViewModelFactory<RoomMemberListViewModel, RoomMemberListViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
private val roomFlow = room.flow()
|
||||
|
||||
init {
|
||||
observeRoomMemberSummaries()
|
||||
|
@ -82,8 +84,8 @@ class RoomMemberListViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
combine(
|
||||
room.flow().liveRoomMembers(roomMemberQueryParams),
|
||||
room.flow()
|
||||
roomFlow.liveRoomMembers(roomMemberQueryParams),
|
||||
roomFlow
|
||||
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||
.unwrap()
|
||||
|
@ -94,8 +96,19 @@ class RoomMemberListViewModel @AssistedInject constructor(
|
|||
copy(roomMemberSummaries = async)
|
||||
}
|
||||
|
||||
roomFlow.liveAreAllMembersLoaded()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
setState {
|
||||
copy(
|
||||
areAllMembersLoaded = it
|
||||
)
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
if (room.roomCryptoService().isEncrypted()) {
|
||||
room.flow().liveRoomMembers(roomMemberQueryParams)
|
||||
roomFlow.liveRoomMembers(roomMemberQueryParams)
|
||||
.flatMapLatest { membersSummary ->
|
||||
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
|
||||
.asFlow()
|
||||
|
@ -138,7 +151,7 @@ class RoomMemberListViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.flow().liveRoomSummary()
|
||||
roomFlow.liveRoomSummary()
|
||||
.unwrap()
|
||||
.execute { async ->
|
||||
copy(roomSummary = async)
|
||||
|
@ -146,7 +159,7 @@ class RoomMemberListViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun observeThirdPartyInvites() {
|
||||
room.flow()
|
||||
roomFlow
|
||||
.liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE), QueryStringValue.IsNotNull)
|
||||
.execute { async ->
|
||||
copy(threePidInvites = async)
|
||||
|
|
|
@ -32,6 +32,7 @@ data class RoomMemberListViewState(
|
|||
val roomId: String,
|
||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||
val roomMemberSummaries: Async<RoomMemberSummaries> = Uninitialized,
|
||||
val areAllMembersLoaded: Boolean = false,
|
||||
val ignoredUserIds: List<String> = emptyList(),
|
||||
val filter: String = "",
|
||||
val threePidInvites: Async<List<Event>> = Uninitialized,
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
|
@ -32,8 +33,6 @@ import im.vector.app.core.extensions.cleanup
|
|||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.databinding.FragmentRecyclerviewWithSearchBinding
|
||||
import im.vector.app.features.roomprofile.members.RoomMemberListAction
|
||||
import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
|
||||
|
@ -45,8 +44,6 @@ import reactivecircus.flowbinding.appcompat.queryTextChanges
|
|||
import javax.inject.Inject
|
||||
|
||||
class SpacePeopleFragment @Inject constructor(
|
||||
private val drawableProvider: DrawableProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val epoxyController: SpacePeopleListController
|
||||
) : VectorBaseFragment<FragmentRecyclerviewWithSearchBinding>(),
|
||||
OnBackPressed, SpacePeopleListController.InteractionListener {
|
||||
|
@ -64,6 +61,7 @@ class SpacePeopleFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun invalidate() = withState(membersViewModel) { memberListState ->
|
||||
views.progressBar.isGone = memberListState.areAllMembersLoaded
|
||||
val memberCount = (memberListState.roomSummary.invoke()?.otherMemberIds?.size ?: 0) + 1
|
||||
|
||||
toolbar?.subtitle = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
|
||||
|
|
|
@ -27,8 +27,26 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:minHeight="0dp"
|
||||
app:title="@string/bottom_action_people"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"/>
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
|
||||
app:title="@string/bottom_action_people" />
|
||||
|
||||
<!-- Trick to remove surrounding padding (clip from wrapping frame) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
style="@style/Widget.Vector.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/memberNameFilter"
|
||||
|
|
|
@ -113,6 +113,25 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Trick to remove surrounding padding (clip from wrapping frame) -->
|
||||
<FrameLayout
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:elevation="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ProgressBar
|
||||
style="@style/Widget.Vector.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<include
|
||||
|
|
Loading…
Reference in a new issue