Skip to content

Commit

Permalink
Fixes #2414 - Move member loading to the room member detail screen, a…
Browse files Browse the repository at this point in the history
…void blocking the whole application
  • Loading branch information
stefanceriu committed Feb 7, 2024
1 parent f9edb75 commit 0941a1f
Show file tree
Hide file tree
Showing 31 changed files with 278 additions and 305 deletions.
1 change: 1 addition & 0 deletions ElementX/Sources/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct Application: App {
} else {
appCoordinator = AppCoordinator(appDelegate: appDelegate)
}

SceneDelegate.windowManager = appCoordinator.windowManager
}

Expand Down
47 changes: 19 additions & 28 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .roomList:
stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated))
case .roomMemberDetails(let userID):
Task {
switch await roomProxy?.getMember(userID: userID) {
case .success(let member):
stateMachine.tryEvent(.presentRoomMemberDetails(member: .init(value: member)))
case .failure(let error):
MXLog.error("[RoomFlowCoordinator] Failed to get member: \(error)")
case .none:
MXLog.error("[RoomFlowCoordinator] Failed to get member: RoomProxy is nil")
}
}
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
case .genericCallLink, .oidcCallback, .settings, .chatBackupSettings:
break
}
Expand Down Expand Up @@ -173,10 +164,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case (.roomMembersList(let roomID), .dismissRoomMembersList):
return .roomDetails(roomID: roomID, isRoot: false)

case (.room(let roomID), .presentRoomMemberDetails(let member)):
return .roomMemberDetails(roomID: roomID, member: member, fromRoomMembersList: false)
case (.roomMembersList(let roomID), .presentRoomMemberDetails(let member)):
return .roomMemberDetails(roomID: roomID, member: member, fromRoomMembersList: true)
case (.room(let roomID), .presentRoomMemberDetails(userID: let userID)):
return .roomMemberDetails(roomID: roomID, userID: userID, fromRoomMembersList: false)
case (.roomMembersList(let roomID), .presentRoomMemberDetails(userID: let userID)):
return .roomMemberDetails(roomID: roomID, userID: userID, fromRoomMembersList: true)
case (.roomMemberDetails(let roomID, _, let fromRoomMembersList), .dismissRoomMemberDetails):
return fromRoomMembersList ? .roomMembersList(roomID: roomID) : .room(roomID: roomID)

Expand Down Expand Up @@ -285,13 +276,13 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case (.roomMembersList, .dismissRoomMembersList, .roomDetails):
break

case (.room, .presentRoomMemberDetails, .roomMemberDetails(_, let member, _)):
presentRoomMemberDetails(member: member.value)
case (.room, .presentRoomMemberDetails, .roomMemberDetails(_, let userID, _)):
presentRoomMemberDetails(userID: userID)
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
break

case (.roomMembersList, .presentRoomMemberDetails, .roomMemberDetails(_, let member, _)):
presentRoomMemberDetails(member: member.value)
case (.roomMembersList, .presentRoomMemberDetails, .roomMemberDetails(_, let userID, _)):
presentRoomMemberDetails(userID: userID)
case (.roomMemberDetails, .dismissRoomMemberDetails, .roomMembersList):
break

Expand Down Expand Up @@ -469,8 +460,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentPollForm(mode: mode))
case .presentLocationViewer(_, let geoURI, let description):
stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI, description: description)))
case .presentRoomMemberDetails(member: let member):
stateMachine.tryEvent(.presentRoomMemberDetails(member: .init(value: member)))
case .presentRoomMemberDetails(userID: let userID):
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
case .presentMessageForwarding(let itemID):
stateMachine.tryEvent(.presentMessageForwarding(itemID: itemID))
case .presentCallScreen:
Expand Down Expand Up @@ -598,7 +589,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .invite:
stateMachine.tryEvent(.presentInviteUsersScreen)
case .selectedMember(let member):
stateMachine.tryEvent(.presentRoomMemberDetails(member: .init(value: member)))
stateMachine.tryEvent(.presentRoomMemberDetails(userID: member.userID))
}
}
.store(in: &cancellables)
Expand Down Expand Up @@ -931,21 +922,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
}

private func presentRoomMemberDetails(member: RoomMemberProxyProtocol) {
private func presentRoomMemberDetails(userID: String) {
guard let roomProxy else {
fatalError()
}

let params = RoomMemberDetailsScreenCoordinatorParameters(roomProxy: roomProxy,
roomMemberProxy: member,
userID: userID,
mediaProvider: userSession.mediaProvider,
userIndicatorController: userIndicatorController)
let coordinator = RoomMemberDetailsScreenCoordinator(parameters: params)

coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .openDirectChat:
case .openDirectChat(let displayName):
let loadingIndicatorIdentifier = "OpenDirectChatLoadingIndicator"

userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorIdentifier,
Expand All @@ -956,12 +947,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
Task { [weak self] in
guard let self else { return }

let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(member.userID)
let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(userID)
switch currentDirectRoom {
case .success(.some(let roomID)):
stateMachine.tryEvent(.presentRoom(roomID: roomID))
case .success(nil):
switch await userSession.clientProxy.createDirectRoom(with: member.userID, expectedRoomName: member.displayName) {
switch await userSession.clientProxy.createDirectRoom(with: userID, expectedRoomName: displayName) {
case .success(let roomID):
analytics.trackCreatedRoom(isDM: true)
stateMachine.tryEvent(.presentRoom(roomID: roomID))
Expand Down Expand Up @@ -1185,7 +1176,7 @@ private extension RoomFlowCoordinator {
case notificationSettings(roomID: String)
case globalNotificationSettings(roomID: String)
case roomMembersList(roomID: String)
case roomMemberDetails(roomID: String, member: HashableRoomMemberWrapper, fromRoomMembersList: Bool)
case roomMemberDetails(roomID: String, userID: String, fromRoomMembersList: Bool)
case inviteUsersScreen(roomID: String, fromRoomMembersList: Bool)
case mediaUploadPicker(roomID: String, source: MediaPickerScreenSource)
case mediaUploadPreview(roomID: String, fileURL: URL)
Expand Down Expand Up @@ -1225,7 +1216,7 @@ private extension RoomFlowCoordinator {
case presentRoomMembersList
case dismissRoomMembersList

case presentRoomMemberDetails(member: HashableRoomMemberWrapper)
case presentRoomMemberDetails(userID: String)
case dismissRoomMemberDetails

case presentInviteUsersScreen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import SwiftUI

struct RoomMemberDetailsScreenCoordinatorParameters {
let roomProxy: RoomProxyProtocol
let roomMemberProxy: RoomMemberProxyProtocol
let userID: String
let mediaProvider: MediaProviderProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}

enum RoomMemberDetailsScreenCoordinatorAction {
case openDirectChat
case openDirectChat(displayName: String?)
}

final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {
Expand All @@ -40,7 +40,7 @@ final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {

init(parameters: RoomMemberDetailsScreenCoordinatorParameters) {
viewModel = RoomMemberDetailsScreenViewModel(roomProxy: parameters.roomProxy,
roomMemberProxy: parameters.roomMemberProxy,
userID: parameters.userID,
mediaProvider: parameters.mediaProvider,
userIndicatorController: parameters.userIndicatorController)
}
Expand All @@ -50,14 +50,16 @@ final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol {
guard let self else { return }

switch action {
case .openDirectChat:
actionsSubject.send(.openDirectChat)
case .openDirectChat(let displayName):
actionsSubject.send(.openDirectChat(displayName: displayName))
}
}
.store(in: &cancellables)
}

func stop() { viewModel.stop() }
func stop() {
viewModel.stop()
}

func toPresentable() -> AnyView {
AnyView(RoomMemberDetailsScreen(context: viewModel.context))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
import Foundation

enum RoomMemberDetailsScreenViewModelAction {
case openDirectChat
case openDirectChat(displayName: String?)
}

struct RoomMemberDetailsScreenViewState: BindableState {
var details: RoomMemberDetails
let userID: String
var memberDetails: RoomMemberDetails?
var isProcessingIgnoreRequest = false

var bindings: RoomMemberDetailsScreenViewStateBindings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,55 @@ typealias RoomMemberDetailsScreenViewModelType = StateStoreViewModel<RoomMemberD

class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, RoomMemberDetailsScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol
private let roomMemberProxy: RoomMemberProxyProtocol
private let userID: String
private let mediaProvider: MediaProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol

private var actionsSubject: PassthroughSubject<RoomMemberDetailsScreenViewModelAction, Never> = .init()

private var roomMemberProxy: RoomMemberProxyProtocol?

var actions: AnyPublisher<RoomMemberDetailsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

init(roomProxy: RoomProxyProtocol,
roomMemberProxy: RoomMemberProxyProtocol,
userID: String,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.roomProxy = roomProxy
self.roomMemberProxy = roomMemberProxy
self.userID = userID
self.mediaProvider = mediaProvider
self.userIndicatorController = userIndicatorController

let initialViewState = RoomMemberDetailsScreenViewState(details: RoomMemberDetails(withProxy: roomMemberProxy),
bindings: .init())
let initialViewState = RoomMemberDetailsScreenViewState(userID: userID, bindings: .init())

super.init(initialViewState: initialViewState, imageProvider: mediaProvider)

showMemberLoadingIndicator()
Task {
defer {
hideMemberLoadingIndicator()
}

switch await roomProxy.getMember(userID: userID) {
case .success(let member):
roomMemberProxy = member
state.memberDetails = RoomMemberDetails(withProxy: member)
case .failure(let error):
state.bindings.alertInfo = .init(id: .unknown)
MXLog.error("[RoomFlowCoordinator] Failed to get member: \(error)")
}
}
}

// MARK: - Public

func stop() {
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
state.bindings.mediaPreviewItem = nil

hideMemberLoadingIndicator()
}

override func process(viewAction: RoomMemberDetailsScreenViewAction) {
Expand All @@ -66,20 +85,28 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
case .displayAvatar:
displayFullScreenAvatar()
case .openDirectChat:
actionsSubject.send(.openDirectChat)
guard let roomMemberProxy else {
fatalError()
}

actionsSubject.send(.openDirectChat(displayName: roomMemberProxy.displayName))
}
}

// MARK: - Private

@MainActor
private func ignoreUser() async {
guard let roomMemberProxy else {
fatalError()
}

state.isProcessingIgnoreRequest = true
let result = await roomMemberProxy.ignoreUser()
state.isProcessingIgnoreRequest = false
switch result {
case .success:
state.details.isIgnored = true
state.memberDetails?.isIgnored = true
updateMembers()
case .failure:
state.bindings.alertInfo = .init(id: .unknown)
Expand All @@ -88,12 +115,16 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro

@MainActor
private func unignoreUser() async {
guard let roomMemberProxy else {
fatalError()
}

state.isProcessingIgnoreRequest = true
let result = await roomMemberProxy.unignoreUser()
state.isProcessingIgnoreRequest = false
switch result {
case .success:
state.details.isIgnored = false
state.memberDetails?.isIgnored = false
updateMembers()
case .failure:
state.bindings.alertInfo = .init(id: .unknown)
Expand All @@ -107,6 +138,10 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
}

private func displayFullScreenAvatar() {
guard let roomMemberProxy else {
fatalError()
}

guard let avatarURL = roomMemberProxy.avatarURL else {
return
}
Expand All @@ -125,4 +160,20 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
}
}
}

// MARK: Loading indicator

private static let loadingIndicatorIdentifier = "RoomMemberLoading"

private func showMemberLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true),
title: L10n.commonLoading,
persistent: true),
delay: .milliseconds(100))
}

private func hideMemberLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ import Combine
protocol RoomMemberDetailsScreenViewModelProtocol {
var actions: AnyPublisher<RoomMemberDetailsScreenViewModelAction, Never> { get }
var context: RoomMemberDetailsScreenViewModelType.Context { get }

func stop()
}
Loading

0 comments on commit 0941a1f

Please sign in to comment.