mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Accept and decline invites (#806)
* Add accept/decline invite api * Fix isDirect logic * Add logic for accepting/decling invites * Add error handling structure * Add decline alert * Add navigation to joined room * Add generic error * Add changelog.d file * Cleanup * Fix failing UTs * Add UTs * Add UI tests * Cleanup * Update ref screenshots * Refactor invitesScreen state * Improve switch handling in UserSessionFlowCoordinator * Fix build error
This commit is contained in:
@@ -31,7 +31,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
var callback: ((HomeScreenViewModelAction) -> Void)?
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
||||
init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) {
|
||||
self.userSession = userSession
|
||||
self.attributedStringBuilder = attributedStringBuilder
|
||||
|
||||
@@ -21,7 +21,9 @@ struct InvitesCoordinatorParameters {
|
||||
let userSession: UserSessionProtocol
|
||||
}
|
||||
|
||||
enum InvitesCoordinatorAction { }
|
||||
enum InvitesCoordinatorAction {
|
||||
case openRoom(withIdentifier: String)
|
||||
}
|
||||
|
||||
final class InvitesCoordinator: CoordinatorProtocol {
|
||||
private let parameters: InvitesCoordinatorParameters
|
||||
@@ -39,10 +41,15 @@ final class InvitesCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actions.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
viewModel.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .openRoom(let roomID):
|
||||
self.actionsSubject.send(.openRoom(withIdentifier: roomID))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
|
||||
@@ -14,10 +14,17 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
enum InvitesViewModelAction { }
|
||||
enum InvitesViewModelAction {
|
||||
case openRoom(withIdentifier: String)
|
||||
}
|
||||
|
||||
struct InvitesViewState: BindableState {
|
||||
var invites: [InvitesRoomDetails]?
|
||||
var bindings: InvitesViewStateBindings = .init()
|
||||
}
|
||||
|
||||
struct InvitesViewStateBindings {
|
||||
var alertInfo: AlertInfo<Bool>?
|
||||
}
|
||||
|
||||
struct InvitesRoomDetails {
|
||||
|
||||
@@ -37,17 +37,21 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
||||
|
||||
override func process(viewAction: InvitesViewAction) {
|
||||
switch viewAction {
|
||||
case .accept:
|
||||
break
|
||||
case .decline:
|
||||
break
|
||||
case .accept(let invite):
|
||||
accept(invite: invite)
|
||||
case .decline(let invite):
|
||||
startDeclineFlow(invite: invite)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var clientProxy: ClientProxyProtocol {
|
||||
userSession.clientProxy
|
||||
}
|
||||
|
||||
private var invitesSummaryProvider: RoomSummaryProviderProtocol? {
|
||||
userSession.clientProxy.invitesSummaryProvider
|
||||
clientProxy.invitesSummaryProvider
|
||||
}
|
||||
|
||||
private func setupSubscriptions() {
|
||||
@@ -57,6 +61,7 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
||||
}
|
||||
|
||||
invitesSummaryProvider.roomListPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] roomSummaries in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -72,7 +77,7 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
||||
|
||||
private func fetchInviter(for roomID: String) {
|
||||
Task {
|
||||
guard let room: RoomProxyProtocol = await self.userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
guard let room: RoomProxyProtocol = await self.clientProxy.roomForIdentifier(roomID) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,6 +90,69 @@ class InvitesViewModel: InvitesViewModelType, InvitesViewModelProtocol {
|
||||
state.invites?[inviteIndex].inviter = inviter
|
||||
}
|
||||
}
|
||||
|
||||
private func startDeclineFlow(invite: InvitesRoomDetails) {
|
||||
let roomPlaceholder = invite.isDirect ? (invite.inviter?.displayName ?? invite.roomDetails.name) : invite.roomDetails.name
|
||||
let title = invite.isDirect ? L10n.screenInvitesDeclineDirectChatTitle : L10n.screenInvitesDeclineChatTitle
|
||||
let message = invite.isDirect ? L10n.screenInvitesDeclineDirectChatMessage(roomPlaceholder) : L10n.screenInvitesDeclineChatMessage(roomPlaceholder)
|
||||
|
||||
state.bindings.alertInfo = .init(id: true,
|
||||
title: title,
|
||||
message: message,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionDecline, role: .destructive, action: { self.decline(invite: invite) }))
|
||||
}
|
||||
|
||||
private func accept(invite: InvitesRoomDetails) {
|
||||
Task {
|
||||
let roomID = invite.roomDetails.id
|
||||
defer {
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(roomID)
|
||||
}
|
||||
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||
|
||||
guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else {
|
||||
displayError(.failedAcceptingInvite)
|
||||
return
|
||||
}
|
||||
|
||||
switch await roomProxy.acceptInvitation() {
|
||||
case .success:
|
||||
actionsSubject.send(.openRoom(withIdentifier: roomID))
|
||||
case .failure(let error):
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func decline(invite: InvitesRoomDetails) {
|
||||
Task {
|
||||
let roomID = invite.roomDetails.id
|
||||
defer {
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(roomID)
|
||||
}
|
||||
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||
|
||||
guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else {
|
||||
displayError(.failedRejectingInvite)
|
||||
return
|
||||
}
|
||||
|
||||
let result = await roomProxy.rejectInvitation()
|
||||
|
||||
if case .failure(let error) = result {
|
||||
displayError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func displayError(_ error: RoomProxyError) {
|
||||
state.bindings.alertInfo = .init(id: true,
|
||||
title: L10n.commonError,
|
||||
message: L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == RoomSummary {
|
||||
|
||||
@@ -35,6 +35,7 @@ struct InvitesScreen: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle(L10n.actionInvitesList)
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -86,9 +86,11 @@ struct InvitesScreenCell: View {
|
||||
HStack(spacing: 12) {
|
||||
Button(L10n.actionDecline, action: declineAction)
|
||||
.buttonStyle(.elementCapsule)
|
||||
.accessibilityIdentifier(A11yIdentifiers.invitesScreen.decline)
|
||||
|
||||
Button(L10n.actionAccept, action: acceptAction)
|
||||
.buttonStyle(.elementCapsuleProminent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.invitesScreen.accept)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user