Invited members in room's members list (#880)

* Add invited people in RoomMembersListScreen

* Optimize main thread perfomance in RoomDetailsScreenViewModel

* Fix member count in RoomDetailsScreenViewModel

* Add warning

* Add performance warnings

* Build members in RoomMembersListScreenViewModel in global queue

* Refactor RoomDetailsScreenViewModel build members process

* Localize strings

* Cleanup

* Fix RoomDetailsScreenViewModelTests UTs

* Fix UI tests

* Add RoomMembersListScreenViewModel UTs

* Add UI tests

* Amend UI tests

* Use clearAndTypeText
This commit is contained in:
Alfonso Grillo
2023-05-12 15:12:59 +02:00
committed by GitHub
parent edc9fe4612
commit 1981270840
28 changed files with 293 additions and 62 deletions
@@ -21,19 +21,32 @@ enum RoomMembersListScreenViewModelAction {
}
struct RoomMembersListScreenViewState: BindableState {
var members: [RoomMemberDetails]
private var joinedMembers: [RoomMemberDetails]
private var invitedMembers: [RoomMemberDetails]
var bindings: RoomMembersListScreenViewStateBindings
init(joinedMembers: [RoomMemberDetails] = [], invitedMembers: [RoomMemberDetails] = [], bindings: RoomMembersListScreenViewStateBindings = .init()) {
self.joinedMembers = joinedMembers
self.invitedMembers = invitedMembers
self.bindings = bindings
}
var visibleMembers: [RoomMemberDetails] {
if bindings.searchQuery.isEmpty {
return members
}
return members.lazy.filter { member in
member.id.localizedCaseInsensitiveContains(bindings.searchQuery) ||
member.name?.localizedCaseInsensitiveContains(bindings.searchQuery) ?? false
}
var visibleJoinedMembers: [RoomMemberDetails] {
joinedMembers.lazy
.filter { member in
member.matches(searchQuery: bindings.searchQuery)
}
}
var visibleInvitedMembers: [RoomMemberDetails] {
invitedMembers.lazy
.filter { member in
member.matches(searchQuery: bindings.searchQuery)
}
}
var joinedMembersCount: Int {
joinedMembers.count
}
}
@@ -47,3 +60,13 @@ struct RoomMembersListScreenViewStateBindings {
enum RoomMembersListScreenViewAction {
case selectMember(id: String)
}
private extension RoomMemberDetails {
func matches(searchQuery: String) -> Bool {
guard !searchQuery.isEmpty else {
return true
}
return id.localizedCaseInsensitiveContains(searchQuery) || name?.localizedCaseInsensitiveContains(searchQuery) ?? false
}
}
@@ -24,13 +24,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
var callback: ((RoomMembersListScreenViewModelAction) -> Void)?
init(mediaProvider: MediaProviderProtocol,
members: [RoomMemberProxyProtocol]) {
init(mediaProvider: MediaProviderProtocol, members: [RoomMemberProxyProtocol]) {
self.mediaProvider = mediaProvider
self.members = members
super.init(initialViewState: .init(members: members.map { RoomMemberDetails(withProxy: $0) },
bindings: .init()),
imageProvider: mediaProvider)
super.init(initialViewState: .init(), imageProvider: mediaProvider)
buildMembersDetails(members: members)
}
// MARK: - Public
@@ -45,4 +44,35 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
callback?(.selectMember(member))
}
}
// MARK: - Private
func buildMembersDetails(members: [RoomMemberProxyProtocol]) {
Task {
let indicatorId = UUID().uuidString
defer {
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(indicatorId)
}
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: indicatorId, type: .modal, title: L10n.commonLoading, persistent: true))
let (invitedMembers, joinedMembers) = await split(members: members)
self.state = .init(joinedMembers: joinedMembers, invitedMembers: invitedMembers)
}
}
func split(members: [RoomMemberProxyProtocol]) async -> (invited: [RoomMemberDetails], joined: [RoomMemberDetails]) {
await Task.detached {
// accessing RoomMember's properties is very slow. We need to do it in a background thread.
let invitedMembers = members
.filter { $0.membership == .invite }
.map(RoomMemberDetails.init(withProxy:))
let joinedMembers = members
.filter { $0.membership == .join }
.map(RoomMemberDetails.init(withProxy:))
return (invitedMembers, joinedMembers)
}
.value
}
}
@@ -25,18 +25,8 @@ struct RoomMembersListScreen: View {
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
Section {
ForEach(context.viewState.visibleMembers) { member in
RoomMembersListScreenMemberCell(member: member, context: context)
.id(member.id)
}
} header: {
Text(L10n.commonMemberCount(context.viewState.members.count))
.foregroundColor(.element.secondaryContent)
.font(.compound.bodyLG)
.padding(.vertical, 12)
}
.padding(.horizontal)
membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle)
membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(context.viewState.joinedMembersCount))
}
}
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
@@ -45,6 +35,24 @@ struct RoomMembersListScreen: View {
.navigationTitle(L10n.commonPeople)
.alert(item: $context.alertInfo) { $0.alert }
}
// MARK: - Private
private func membersSection(data: [RoomMemberDetails], sectionTitle: String) -> some View {
Section {
ForEach(data, id: \.id) { member in
RoomMembersListScreenMemberCell(member: member, context: context)
}
} header: {
if !data.isEmpty {
Text(sectionTitle)
.foregroundColor(.element.secondaryContent)
.font(.compound.bodyLG)
.padding(.vertical, 12)
}
}
.padding(.horizontal)
}
}
// MARK: - Previews