Files
letro-ios/ElementX/Sources/Screens/ResolveVerifiedUserSendFailureScreen/ResolveVerifiedUserSendFailureScreenViewModel.swift
Doug 84ff9ee2e2 Adopt StateStoreViewModelV2 in more screens. (#4275)
* Use StateStoreViewModelV2 in AnalyticsPromptScreen.

* Use StateStoreViewModelV2 in UserProfileScreen.

* Use StateStoreViewModelV2 in MediaUploadPreviewScreen.

* Use StateStoreViewModelV2 in the Roles & Permissions screens.

* Add the asyncStream variant of deferFailure.

* Use StateStoreViewModelV2 in BlockedUsersScreen.

* Use StateStoreViewModelV2 in ManageRoomMemberSheet.

* Use StateStoreViewModelV2 in ResolveVerifiedUserSendFailureScreen.

* Use StateStoreViewModelV2 in ReportContentScreen.

* Use StateStoreViewModelV2 in ReportRoomScreen.

* Use StateStoreViewModelV2 in StaticLocationScreen.

* Use StateStoreViewModelV2 in EmojiPickerScreen.

* Use StateStoreViewModelV2 in PollFormScreen.

* Use StateStoreViewModelV2 in DeclineAndBlockScreen.

* Use StateStoreViewModelV2 in RoomDetailsScreen.

* Fix a random compilation error that just popped up 🤷‍♂️

* Fix expectation message.
2025-07-02 15:13:42 +01:00

143 lines
5.8 KiB
Swift

//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
import SwiftUI
typealias ResolveVerifiedUserSendFailureScreenViewModelType = StateStoreViewModelV2<ResolveVerifiedUserSendFailureScreenViewState, ResolveVerifiedUserSendFailureScreenViewAction>
class ResolveVerifiedUserSendFailureScreenViewModel: ResolveVerifiedUserSendFailureScreenViewModelType, ResolveVerifiedUserSendFailureScreenViewModelProtocol {
private let iterator: VerifiedUserSendFailureIterator
private let failure: TimelineItemSendFailure.VerifiedUser
private let sendHandle: SendHandleProxy
private let roomProxy: JoinedRoomProxyProtocol
private var members: [String: RoomMemberProxyProtocol]
private let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<ResolveVerifiedUserSendFailureScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<ResolveVerifiedUserSendFailureScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(failure: TimelineItemSendFailure.VerifiedUser,
sendHandle: SendHandleProxy,
roomProxy: JoinedRoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
iterator = switch failure {
case .hasUnsignedDevice(let devices): UnsignedDeviceFailureIterator(devices: devices)
case .changedIdentity(let users): ChangedIdentityFailureIterator(users: users)
}
self.failure = failure
self.sendHandle = sendHandle
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
members = Dictionary(uniqueKeysWithValues: roomProxy.membersPublisher.value.map { ($0.userID, $0) })
guard let (userID, failure) = iterator.next() else {
MXLog.error("There aren't any known users/devices to resolve the failure with.")
fatalError("There aren't any known users/devices to resolve the failure with.")
}
super.init(initialViewState: ResolveVerifiedUserSendFailureScreenViewState(currentFailure: failure,
currentMemberDisplayName: members[userID]?.displayName ?? userID,
isYou: userID == roomProxy.ownUserID))
}
// MARK: Public
override func process(viewAction: ResolveVerifiedUserSendFailureScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .resolveAndResend:
Task { await resolveAndResend() }
case .resend:
Task { await resend() }
case .cancel:
actionsSubject.send(.dismiss)
}
}
private func resolveAndResend() async {
let result = switch failure {
case .hasUnsignedDevice(let devices):
await roomProxy.ignoreDeviceTrustAndResend(devices: devices, sendHandle: sendHandle)
case .changedIdentity(let users):
await roomProxy.withdrawVerificationAndResend(userIDs: users, sendHandle: sendHandle)
}
if case let .failure(error) = result {
MXLog.error("Failed to resolve send failure: \(error)")
showFailureIndicator()
return
}
if let (userID, failure) = iterator.next() {
state.currentMemberDisplayName = members[userID]?.displayName ?? userID
state.currentFailure = failure
state.isYou = userID == roomProxy.ownUserID
} else {
actionsSubject.send(.dismiss)
}
}
private func resend() async {
switch await sendHandle.resend() {
case .success:
actionsSubject.send(.dismiss)
case .failure(let error):
MXLog.error("Failed to resend message: \(error)")
showFailureIndicator()
}
}
private static let failureIndicatorIdentifier = "\(ResolveVerifiedUserSendFailureScreenViewModel.self)-Failure"
private func showFailureIndicator() {
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.failureIndicatorIdentifier,
type: .toast,
title: L10n.errorUnknown,
iconName: "xmark"))
}
}
// MARK: - Iterators
@MainActor
private protocol VerifiedUserSendFailureIterator {
func next() -> (userID: String, failure: TimelineItemSendFailure.VerifiedUser)?
}
private class UnsignedDeviceFailureIterator: VerifiedUserSendFailureIterator {
private var iterator: [String: [String]].Iterator
init(devices: [String: [String]]) {
iterator = devices.makeIterator()
}
func next() -> (userID: String, failure: TimelineItemSendFailure.VerifiedUser)? {
guard let nextUserDevices = iterator.next() else { return nil }
return (nextUserDevices.key, .hasUnsignedDevice(devices: [nextUserDevices.key: nextUserDevices.value]))
}
}
private class ChangedIdentityFailureIterator: VerifiedUserSendFailureIterator {
private var iterator: [String].Iterator
init(users: [String]) {
iterator = users.makeIterator()
}
func next() -> (userID: String, failure: TimelineItemSendFailure.VerifiedUser)? {
guard let nextUserID = iterator.next() else { return nil }
return (nextUserID, .changedIdentity(users: [nextUserID]))
}
}