Fixes element-hq/element-meta/issues/2525 - Display a warning when a user's pinned identity changes

This commit is contained in:
Stefan Ceriu
2024-10-01 08:41:15 +03:00
parent 70652ed7be
commit 794d0eead1
30 changed files with 678 additions and 131 deletions
@@ -7,18 +7,24 @@
import Combine
import Foundation
import MatrixRustSDK
import OrderedCollections
import SwiftUI
typealias RoomScreenViewModelType = StateStoreViewModel<RoomScreenViewState, RoomScreenViewAction>
class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol {
private let clientProxy: ClientProxyProtocol
private let roomProxy: JoinedRoomProxyProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analyticsService: AnalyticsService
private let pinnedEventStringBuilder: RoomEventStringBuilder
private let userIndicatorController: UserIndicatorControllerProtocol
private var initialSelectedPinnedEventID: String?
private let pinnedEventStringBuilder: RoomEventStringBuilder
private var identityPinningViolations = [String: RoomMemberProxyProtocol]()
private let actionsSubject: PassthroughSubject<RoomScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<RoomScreenViewModelAction, Never> {
@@ -43,17 +49,22 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
}
init(roomProxy: JoinedRoomProxyProtocol,
init(clientProxy: ClientProxyProtocol,
roomProxy: JoinedRoomProxyProtocol,
initialSelectedPinnedEventID: String?,
mediaProvider: MediaProviderProtocol,
ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>,
appMediator: AppMediatorProtocol,
appSettings: AppSettings,
analyticsService: AnalyticsService) {
analyticsService: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol) {
self.clientProxy = clientProxy
self.roomProxy = roomProxy
self.appMediator = appMediator
self.appSettings = appSettings
self.analyticsService = analyticsService
self.userIndicatorController = userIndicatorController
self.initialSelectedPinnedEventID = initialSelectedPinnedEventID
pinnedEventStringBuilder = .pinnedEventStringBuilder(userID: roomProxy.ownUserID)
@@ -87,6 +98,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
actionsSubject.send(.displayCall)
actionsSubject.send(.removeComposerFocus)
analyticsService.trackInteraction(name: .MobileRoomCallButton)
case .footerViewAction(let action):
switch action {
case .resolvePinViolation(let userID):
Task { await resolveIdentityPinningViolation(userID) }
}
}
}
@@ -98,6 +114,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.pinnedEventsBannerState.setSelectedPinnedEventID(eventID)
}
// MARK: - Private
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
let roomInfoSubscription = roomProxy
.actionsPublisher
@@ -124,6 +142,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
.store(in: &cancellables)
let identityStatusChangesPublisher = roomProxy.identityStatusChangesPublisher.receive(on: DispatchQueue.main)
Task { [weak self] in
for await changes in identityStatusChangesPublisher.values {
guard !Task.isCancelled else {
return
}
await self?.processIdentityStatusChanges(changes)
}
}
.store(in: &cancellables)
appMediator.networkMonitor.reachabilityPublisher
.filter { $0 == .reachable }
.receive(on: DispatchQueue.main)
@@ -141,6 +172,43 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.store(in: &cancellables)
}
private func processIdentityStatusChanges(_ changes: [IdentityStatusChange]) async {
for change in changes {
switch change.changedTo {
case .pinned:
identityPinningViolations[change.userId] = nil
case .pinViolation:
guard case let .success(member) = await roomProxy.getMember(userID: change.userId) else {
MXLog.error("Failed retrieving room member for identity status change: \(change)")
continue
}
identityPinningViolations[change.userId] = member
default:
break
}
}
if let member = identityPinningViolations.values.first {
state.footerDetails = .pinViolation(member: member,
learnMoreURL: appSettings.identityPinningViolationDetailsURL)
} else {
state.footerDetails = nil
}
}
private func resolveIdentityPinningViolation(_ userID: String) async {
defer {
hideLoadingIndicator()
}
showLoadingIndicator()
if case .failure = await clientProxy.pinUserIdentity(userID) {
userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError)
}
}
private func buildPinnedEventContents(timelineItems: [TimelineItemProxy]) {
var pinnedEventContents = OrderedDictionary<String, AttributedString>()
@@ -190,16 +258,30 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
}
}
// MARK: Loading indicators
private static let loadingIndicatorIdentifier = "\(RoomScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(.init(id: Self.loadingIndicatorIdentifier, type: .toast, title: L10n.commonLoading))
}
private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
}
extension RoomScreenViewModel {
static func mock(roomProxyMock: JoinedRoomProxyMock) -> RoomScreenViewModel {
RoomScreenViewModel(roomProxy: roomProxyMock,
RoomScreenViewModel(clientProxy: ClientProxyMock(),
roomProxy: roomProxyMock,
initialSelectedPinnedEventID: nil,
mediaProvider: MediaProviderMock(configuration: .init()),
ongoingCallRoomIDPublisher: .init(.init(nil)),
appMediator: AppMediatorMock.default,
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics)
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}
}