mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
feat: report a room
updated tests updated tests added a feature flag to report room removed delay rename
This commit is contained in:
@@ -56,6 +56,7 @@ final class AppSettings {
|
||||
case fuzzyRoomListSearchEnabled
|
||||
case enableOnlySignedDeviceIsolationMode
|
||||
case knockingEnabled
|
||||
case reportRoomEnabled
|
||||
}
|
||||
|
||||
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||
@@ -330,6 +331,8 @@ final class AppSettings {
|
||||
@UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var knockingEnabled
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.reportRoomEnabled, defaultValue: false, storageType: .userDefaults(store)) var reportRoomEnabled
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - Shared
|
||||
|
||||
@@ -414,6 +414,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
return .securityAndPrivacy(previousState: fromState)
|
||||
case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen):
|
||||
return previousState
|
||||
|
||||
case (.roomDetails, .presentReportRoomScreen):
|
||||
return .reportRoom(previousState: fromState)
|
||||
case (.reportRoom(let previousState), .dismissReportRoomScreen):
|
||||
return previousState
|
||||
|
||||
default:
|
||||
return nil
|
||||
@@ -585,6 +590,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentSecurityAndPrivacyScreen()
|
||||
case (.securityAndPrivacy, .dismissSecurityAndPrivacyScreen, .roomDetails):
|
||||
break
|
||||
|
||||
case (.roomDetails, .presentReportRoomScreen, .reportRoom):
|
||||
presentReportRoom()
|
||||
case (.reportRoom, .dismissReportRoomScreen, .roomDetails):
|
||||
break
|
||||
|
||||
// Child flow
|
||||
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
|
||||
@@ -869,6 +879,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
|
||||
case .presentRecipientDetails(let userID):
|
||||
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
||||
case .presentReportRoomScreen:
|
||||
stateMachine.tryEvent(.presentReportRoomScreen)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -1516,6 +1528,29 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator)
|
||||
}
|
||||
|
||||
private func presentReportRoom() {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
|
||||
userIndicatorController: userIndicatorController))
|
||||
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss(let shouldLeaveRoom):
|
||||
if shouldLeaveRoom {
|
||||
stateMachine.tryEvent(.dismissFlow)
|
||||
}
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
stackCoordinator.setRootCoordinator(coordinator)
|
||||
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissReportRoomScreen)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Other flows
|
||||
|
||||
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
|
||||
@@ -1686,6 +1721,7 @@ private extension RoomFlowCoordinator {
|
||||
case knockRequestsList(previousState: State)
|
||||
case mediaEventsTimeline(previousState: State)
|
||||
case securityAndPrivacy(previousState: State)
|
||||
case reportRoom(previousState: State)
|
||||
|
||||
/// A child flow is in progress.
|
||||
case presentingChild(childRoomID: String, previousState: State)
|
||||
@@ -1772,5 +1808,8 @@ private extension RoomFlowCoordinator {
|
||||
|
||||
case presentSecurityAndPrivacyScreen
|
||||
case dismissSecurityAndPrivacyScreen
|
||||
|
||||
case presentReportRoomScreen
|
||||
case dismissReportRoomScreen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
case .roomDetails(let roomID):
|
||||
if stateMachine.state.selectedRoomID == roomID {
|
||||
if stateMachine.state.roomListSelectedRoomID == roomID {
|
||||
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||
} else {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .roomDetails), userInfo: .init(animated: animated))
|
||||
@@ -245,8 +245,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.initial, .start, .roomList):
|
||||
presentHomeScreen()
|
||||
attemptStartingOnboarding()
|
||||
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
||||
if selectedRoomID == roomID,
|
||||
case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
||||
if roomListSelectedRoomID == roomID,
|
||||
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
|
||||
let roomFlowCoordinator {
|
||||
let route: AppRoute = switch entryPoint {
|
||||
@@ -307,9 +307,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.userProfileScreen, .dismissedUserProfileScreen, .roomList):
|
||||
break
|
||||
|
||||
case (.roomList(let selectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)):
|
||||
case (.roomList, .presentReportRoomScreen(let roomID), .reportRoomScreen):
|
||||
Task { await self.presentReportRoom(for: roomID) }
|
||||
case (.reportRoomScreen, .dismissedReportRoomScreen, .roomList):
|
||||
break
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)):
|
||||
Task {
|
||||
if selectedRoomID != nil {
|
||||
if roomListSelectedRoomID != nil {
|
||||
self.clearRoute(animated: animated)
|
||||
try? await Task.sleep(for: .seconds(1.5))
|
||||
}
|
||||
@@ -326,8 +331,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
switch context.toState {
|
||||
case .roomList(let selectedRoomID):
|
||||
self?.selectedRoomSubject.send(selectedRoomID)
|
||||
case .roomList(let roomListSelectedRoomID):
|
||||
self?.selectedRoomSubject.send(roomListSelectedRoomID)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -496,9 +501,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
|
||||
case .presentRoomDetails(let roomID):
|
||||
handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
||||
case .presentReportRoom(let roomID):
|
||||
stateMachine.processEvent(.presentReportRoomScreen(roomID: roomID))
|
||||
case .roomLeft(let roomID):
|
||||
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state,
|
||||
selectedRoomID == roomID {
|
||||
if case .roomList(roomListSelectedRoomID: let roomListSelectedRoomID) = stateMachine.state,
|
||||
roomListSelectedRoomID == roomID {
|
||||
clearRoute(animated: true)
|
||||
}
|
||||
case .presentSettingsScreen:
|
||||
@@ -528,6 +535,35 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
|
||||
}
|
||||
|
||||
private func presentReportRoom(for roomID: String) async {
|
||||
guard let roomProxyType = await userSession.clientProxy.roomForIdentifier(roomID),
|
||||
case let .joined(roomProxy) = roomProxyType else {
|
||||
MXLog.error("Failed to get room proxy for room: \(roomID)")
|
||||
return
|
||||
}
|
||||
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController))
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss(let shouldLeaveRoom):
|
||||
if shouldLeaveRoom,
|
||||
case .roomList(let roomListSelectedRoomID) = stateMachine.state,
|
||||
roomListSelectedRoomID == roomID {
|
||||
clearRoute(animated: true)
|
||||
}
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
|
||||
self?.stateMachine.processEvent(.dismissedReportRoomScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private func runLogoutFlow() async {
|
||||
let secureBackupController = userSession.clientProxy.secureBackupController
|
||||
|
||||
|
||||
@@ -15,49 +15,53 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
/// The initial state, used before the coordinator starts
|
||||
case initial
|
||||
|
||||
/// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any)
|
||||
case roomList(selectedRoomID: String?)
|
||||
/// Showing the home screen. The `roomListSelectedRoomID` represents the timeline shown on the detail panel (if any)
|
||||
case roomList(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the feedback screen.
|
||||
case feedbackScreen(selectedRoomID: String?)
|
||||
case feedbackScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the settings screen
|
||||
case settingsScreen(selectedRoomID: String?)
|
||||
case settingsScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the recovery key screen.
|
||||
case recoveryKeyScreen(selectedRoomID: String?)
|
||||
case recoveryKeyScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the encryption reset flow.
|
||||
case encryptionResetFlow(selectedRoomID: String?)
|
||||
case encryptionResetFlow(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the start chat screen
|
||||
case startChatScreen(selectedRoomID: String?)
|
||||
case startChatScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the logout flows
|
||||
case logoutConfirmationScreen(selectedRoomID: String?)
|
||||
case logoutConfirmationScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing Room Directory Search screen
|
||||
case roomDirectorySearchScreen(selectedRoomID: String?)
|
||||
case roomDirectorySearchScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
/// Showing the user profile screen. This screen clears the navigation.
|
||||
case userProfileScreen
|
||||
|
||||
/// Showing the report room screen, for the given room identrifier
|
||||
case reportRoomScreen(roomListSelectedRoomID: String?)
|
||||
|
||||
case shareExtensionRoomList(sharePayload: ShareExtensionPayload)
|
||||
|
||||
/// The selected room ID from the state if available.
|
||||
var selectedRoomID: String? {
|
||||
var roomListSelectedRoomID: String? {
|
||||
switch self {
|
||||
case .initial, .userProfileScreen, .shareExtensionRoomList:
|
||||
nil
|
||||
case .roomList(let selectedRoomID),
|
||||
.feedbackScreen(let selectedRoomID),
|
||||
.settingsScreen(let selectedRoomID),
|
||||
.recoveryKeyScreen(let selectedRoomID),
|
||||
.encryptionResetFlow(let selectedRoomID),
|
||||
.startChatScreen(let selectedRoomID),
|
||||
.logoutConfirmationScreen(let selectedRoomID),
|
||||
.roomDirectorySearchScreen(let selectedRoomID):
|
||||
selectedRoomID
|
||||
case .roomList(let roomListSelectedRoomID),
|
||||
.feedbackScreen(let roomListSelectedRoomID),
|
||||
.settingsScreen(let roomListSelectedRoomID),
|
||||
.recoveryKeyScreen(let roomListSelectedRoomID),
|
||||
.encryptionResetFlow(let roomListSelectedRoomID),
|
||||
.startChatScreen(let roomListSelectedRoomID),
|
||||
.logoutConfirmationScreen(let roomListSelectedRoomID),
|
||||
.roomDirectorySearchScreen(let roomListSelectedRoomID),
|
||||
.reportRoomScreen(let roomListSelectedRoomID):
|
||||
roomListSelectedRoomID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +125,9 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
|
||||
case showShareExtensionRoomList(sharePayload: ShareExtensionPayload)
|
||||
case dismissedShareExtensionRoomList
|
||||
|
||||
case presentReportRoomScreen(roomID: String)
|
||||
case dismissedReportRoomScreen
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
@@ -140,59 +147,64 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
}
|
||||
|
||||
private func configure() {
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(selectedRoomID: nil)])
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(roomListSelectedRoomID: nil)])
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, _ in
|
||||
switch (fromState, event) {
|
||||
case (.roomList, .selectRoom(let roomID, _, _)):
|
||||
return .roomList(selectedRoomID: roomID)
|
||||
return .roomList(roomListSelectedRoomID: roomID)
|
||||
case (.roomList, .deselectRoom):
|
||||
return .roomList(selectedRoomID: nil)
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showSettingsScreen):
|
||||
return .settingsScreen(selectedRoomID: selectedRoomID)
|
||||
case (.settingsScreen(let selectedRoomID), .dismissedSettingsScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .showSettingsScreen):
|
||||
return .settingsScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.settingsScreen(let roomListSelectedRoomID), .dismissedSettingsScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .feedbackScreen):
|
||||
return .feedbackScreen(selectedRoomID: selectedRoomID)
|
||||
case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
|
||||
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.feedbackScreen(let roomListSelectedRoomID), .dismissedFeedbackScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showRecoveryKeyScreen):
|
||||
return .recoveryKeyScreen(selectedRoomID: selectedRoomID)
|
||||
case (.recoveryKeyScreen(let selectedRoomID), .dismissedRecoveryKeyScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .showRecoveryKeyScreen):
|
||||
return .recoveryKeyScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.recoveryKeyScreen(let roomListSelectedRoomID), .dismissedRecoveryKeyScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .startEncryptionResetFlow):
|
||||
return .encryptionResetFlow(selectedRoomID: selectedRoomID)
|
||||
case (.encryptionResetFlow(let selectedRoomID), .finishedEncryptionResetFlow):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .startEncryptionResetFlow):
|
||||
return .encryptionResetFlow(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.encryptionResetFlow(let roomListSelectedRoomID), .finishedEncryptionResetFlow):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showStartChatScreen):
|
||||
return .startChatScreen(selectedRoomID: selectedRoomID)
|
||||
case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .showStartChatScreen):
|
||||
return .startChatScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showLogoutConfirmationScreen):
|
||||
return .logoutConfirmationScreen(selectedRoomID: selectedRoomID)
|
||||
case (.logoutConfirmationScreen(let selectedRoomID), .dismissedLogoutConfirmationScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .showLogoutConfirmationScreen):
|
||||
return .logoutConfirmationScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.logoutConfirmationScreen(let roomListSelectedRoomID), .dismissedLogoutConfirmationScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen):
|
||||
return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID)
|
||||
case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen):
|
||||
return .roomList(selectedRoomID: selectedRoomID)
|
||||
case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
|
||||
return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
case (_, .showUserProfileScreen):
|
||||
return .userProfileScreen
|
||||
case (.userProfileScreen, .dismissedUserProfileScreen):
|
||||
return .roomList(selectedRoomID: nil)
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
|
||||
case (.roomList, .showShareExtensionRoomList(let sharePayload)):
|
||||
return .shareExtensionRoomList(sharePayload: sharePayload)
|
||||
case (.shareExtensionRoomList, .dismissedShareExtensionRoomList):
|
||||
return .roomList(selectedRoomID: nil)
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .presentReportRoomScreen):
|
||||
return .reportRoomScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.reportRoomScreen(let roomListSelectedRoomID), .dismissedReportRoomScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
|
||||
default:
|
||||
return nil
|
||||
@@ -231,8 +243,8 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
/// Flag indicating the machine is displaying room screen with given room identifier
|
||||
func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool {
|
||||
switch stateMachine.state {
|
||||
case .roomList(let selectedRoomID):
|
||||
return roomID == selectedRoomID
|
||||
case .roomList(let roomListSelectedRoomID):
|
||||
return roomID == roomListSelectedRoomID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6689,6 +6689,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
|
||||
return reportContentReasonReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - reportRoom
|
||||
|
||||
var reportRoomReasonUnderlyingCallsCount = 0
|
||||
var reportRoomReasonCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return reportRoomReasonUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = reportRoomReasonUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
reportRoomReasonUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
reportRoomReasonUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var reportRoomReasonCalled: Bool {
|
||||
return reportRoomReasonCallsCount > 0
|
||||
}
|
||||
var reportRoomReasonReceivedReason: String?
|
||||
var reportRoomReasonReceivedInvocations: [String?] = []
|
||||
|
||||
var reportRoomReasonUnderlyingReturnValue: Result<Void, RoomProxyError>!
|
||||
var reportRoomReasonReturnValue: Result<Void, RoomProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return reportRoomReasonUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<Void, RoomProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = reportRoomReasonUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
reportRoomReasonUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
reportRoomReasonUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var reportRoomReasonClosure: ((String?) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError> {
|
||||
reportRoomReasonCallsCount += 1
|
||||
reportRoomReasonReceivedReason = reason
|
||||
DispatchQueue.main.async {
|
||||
self.reportRoomReasonReceivedInvocations.append(reason)
|
||||
}
|
||||
if let reportRoomReasonClosure = reportRoomReasonClosure {
|
||||
return await reportRoomReasonClosure(reason)
|
||||
} else {
|
||||
return reportRoomReasonReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - leaveRoom
|
||||
|
||||
var leaveRoomUnderlyingCallsCount = 0
|
||||
|
||||
@@ -17,6 +17,7 @@ struct HomeScreenCoordinatorParameters {
|
||||
enum HomeScreenCoordinatorAction {
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentRoomDetails(roomIdentifier: String)
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case presentSettingsScreen
|
||||
case presentFeedbackScreen
|
||||
@@ -58,6 +59,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
case .presentRoomDetails(roomIdentifier: let roomIdentifier):
|
||||
actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier))
|
||||
case .presentReportRoom(let roomIdentifier):
|
||||
actionsSubject.send(.presentReportRoom(roomIdentifier: roomIdentifier))
|
||||
case .roomLeft(roomIdentifier: let roomIdentifier):
|
||||
actionsSubject.send(.roomLeft(roomIdentifier: roomIdentifier))
|
||||
case .presentFeedbackScreen:
|
||||
|
||||
@@ -12,6 +12,7 @@ import UIKit
|
||||
enum HomeScreenViewModelAction: Equatable {
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentRoomDetails(roomIdentifier: String)
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case presentSecureBackupSettings
|
||||
case presentRecoveryKeyScreen
|
||||
@@ -29,6 +30,7 @@ enum HomeScreenViewAction {
|
||||
case showRoomDetails(roomIdentifier: String)
|
||||
case leaveRoom(roomIdentifier: String)
|
||||
case confirmLeaveRoom(roomIdentifier: String)
|
||||
case reportRoom(roomIdentifier: String)
|
||||
case showSettings
|
||||
case startChat
|
||||
case setupRecovery
|
||||
@@ -98,6 +100,10 @@ struct HomeScreenViewState: BindableState {
|
||||
|
||||
var selectedRoomID: String?
|
||||
|
||||
var hideInviteAvatars = false
|
||||
|
||||
var reportRoomEnabled = false
|
||||
|
||||
var visibleRooms: [HomeScreenRoom] {
|
||||
if roomListMode == .skeletons {
|
||||
return placeholderRooms
|
||||
@@ -105,9 +111,7 @@ struct HomeScreenViewState: BindableState {
|
||||
|
||||
return rooms
|
||||
}
|
||||
|
||||
var hideInviteAvatars = false
|
||||
|
||||
|
||||
var bindings = HomeScreenViewStateBindings()
|
||||
|
||||
var placeholderRooms: [HomeScreenRoom] {
|
||||
|
||||
@@ -105,6 +105,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.hideInviteAvatars, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
appSettings.$reportRoomEnabled
|
||||
.weakAssign(to: \.state.reportRoomEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
|
||||
let searchQuery = context.$viewState.map(\.bindings.searchQuery)
|
||||
let activeFilters = context.$viewState.map(\.bindings.filtersState.activeFilters)
|
||||
@@ -136,12 +140,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
switch viewAction {
|
||||
case .selectRoom(let roomIdentifier):
|
||||
actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
case .showRoomDetails(roomIdentifier: let roomIdentifier):
|
||||
case .showRoomDetails(let roomIdentifier):
|
||||
actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier))
|
||||
case .leaveRoom(roomIdentifier: let roomIdentifier):
|
||||
case .leaveRoom(let roomIdentifier):
|
||||
startLeaveRoomProcess(roomID: roomIdentifier)
|
||||
case .confirmLeaveRoom(roomIdentifier: let roomIdentifier):
|
||||
case .confirmLeaveRoom(let roomIdentifier):
|
||||
Task { await leaveRoom(roomID: roomIdentifier) }
|
||||
case .reportRoom(let roomIdentifier):
|
||||
actionsSubject.send(.presentReportRoom(roomIdentifier: roomIdentifier))
|
||||
case .showSettings:
|
||||
actionsSubject.send(.presentSettingsScreen)
|
||||
case .setupRecovery:
|
||||
|
||||
@@ -69,6 +69,14 @@ struct HomeScreenRoomList: View {
|
||||
Label(L10n.commonSettings, icon: \.settings)
|
||||
}
|
||||
|
||||
if context.viewState.reportRoomEnabled {
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .reportRoom(roomIdentifier: room.id))
|
||||
} label: {
|
||||
Label(L10n.actionReportRoom, icon: \.chatProblem)
|
||||
}
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .leaveRoom(roomIdentifier: room.id))
|
||||
} label: {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// periphery:ignore:all - this is just a reportRoom remove this comment once generating the final file
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct ReportRoomScreenCoordinatorParameters {
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
enum ReportRoomScreenCoordinatorAction {
|
||||
case dismiss(shouldLeaveRoom: Bool)
|
||||
}
|
||||
|
||||
final class ReportRoomScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: ReportRoomScreenCoordinatorParameters
|
||||
private let viewModel: ReportRoomScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ReportRoomScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ReportRoomScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: ReportRoomScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = ReportRoomScreenViewModel(roomProxy: parameters.roomProxy,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss(let shouldLeaveRoom):
|
||||
actionsSubject.send(.dismiss(shouldLeaveRoom: shouldLeaveRoom))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(ReportRoomScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
enum ReportRoomScreenViewModelAction: Equatable {
|
||||
case dismiss(shouldLeaveRoom: Bool)
|
||||
}
|
||||
|
||||
struct ReportRoomScreenViewState: BindableState {
|
||||
var bindings = ReportRoomScreenViewStateBindings()
|
||||
}
|
||||
|
||||
struct ReportRoomScreenViewStateBindings {
|
||||
var reason = ""
|
||||
var shouldLeaveRoom = false
|
||||
var alert: AlertInfo<ReportRoomScreenAlertType>?
|
||||
}
|
||||
|
||||
enum ReportRoomScreenViewAction {
|
||||
case report
|
||||
case dismiss
|
||||
}
|
||||
|
||||
enum ReportRoomScreenAlertType {
|
||||
case leaveRoomFailed
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// 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 ReportRoomScreenViewModelType = StateStoreViewModel<ReportRoomScreenViewState, ReportRoomScreenViewAction>
|
||||
|
||||
class ReportRoomScreenViewModel: ReportRoomScreenViewModelType, ReportRoomScreenViewModelProtocol {
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ReportRoomScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ReportRoomScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(roomProxy: JoinedRoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
super.init(initialViewState: ReportRoomScreenViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ReportRoomScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .report:
|
||||
Task { await report() }
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss(shouldLeaveRoom: false))
|
||||
}
|
||||
}
|
||||
|
||||
private func report() async {
|
||||
showLoadingIndicator()
|
||||
let result = await roomProxy.reportRoom(reason: state.bindings.reason.isBlank ? nil : state.bindings.reason)
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
if state.bindings.shouldLeaveRoom {
|
||||
await leaveRoom(showLoading: false)
|
||||
} else {
|
||||
hideLoadingIndicator()
|
||||
userIndicatorController.submitIndicator(.init(title: L10n.dialogRoomReported, iconName: "checkmark"))
|
||||
actionsSubject.send(.dismiss(shouldLeaveRoom: false))
|
||||
}
|
||||
case .failure:
|
||||
hideLoadingIndicator()
|
||||
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoom(showLoading: Bool) async {
|
||||
if showLoading {
|
||||
showLoadingIndicator()
|
||||
}
|
||||
|
||||
let result = await roomProxy.leaveRoom()
|
||||
hideLoadingIndicator()
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
userIndicatorController.submitIndicator(.init(title: L10n.dialogRoomReportedAndLeft, iconName: "checkmark"))
|
||||
actionsSubject.send(.dismiss(shouldLeaveRoom: true))
|
||||
case .failure:
|
||||
state.bindings.alert = .init(id: .leaveRoomFailed,
|
||||
title: L10n.screenReportRoomLeaveFailedAlertTitle,
|
||||
message: L10n.screenReportRoomLeaveFailedAlertMessage,
|
||||
primaryButton: .init(title: L10n.actionDismiss, role: .cancel) { [weak self] in self?.actionsSubject.send(.dismiss(shouldLeaveRoom: false)) },
|
||||
secondaryButton: .init(title: L10n.actionRetry) { [weak self] in Task { await self?.leaveRoom(showLoading: true) } })
|
||||
}
|
||||
}
|
||||
|
||||
private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading"
|
||||
|
||||
private func showLoadingIndicator() {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
|
||||
title: L10n.commonLoading,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func hideLoadingIndicator() {
|
||||
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@MainActor
|
||||
protocol ReportRoomScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ReportRoomScreenViewModelAction, Never> { get }
|
||||
var context: ReportRoomScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// 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 Compound
|
||||
import SwiftUI
|
||||
|
||||
struct ReportRoomScreen: View {
|
||||
@ObservedObject var context: ReportRoomScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
reasonSection
|
||||
leaveRoomSection
|
||||
}
|
||||
.compoundList()
|
||||
.navigationTitle(L10n.screenReportRoomTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { toolbar }
|
||||
.alert(item: $context.alert)
|
||||
}
|
||||
|
||||
private var reasonSection: some View {
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.screenReportRoomReasonPlaceholder),
|
||||
kind: .textField(text: $context.reason, axis: .vertical))
|
||||
.lineLimit(4, reservesSpace: true)
|
||||
} footer: {
|
||||
Text(L10n.screenReportRoomReasonFooter)
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
|
||||
private var leaveRoomSection: some View {
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.actionLeaveRoom),
|
||||
kind: .toggle($context.shouldLeaveRoom))
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .dismiss)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(L10n.actionReport) {
|
||||
context.send(viewAction: .report)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct ReportRoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = ReportRoomScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
ReportRoomScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
||||
case presentMediaEventsTimeline
|
||||
case presentKnockingRequestsListScreen
|
||||
case presentSecurityAndPrivacyScreen
|
||||
case presentReportRoomScreen
|
||||
}
|
||||
|
||||
final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -91,6 +92,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentSecurityAndPrivacyScreen)
|
||||
case .requestRecipientDetailsPresentation(let userID):
|
||||
actionsSubject.send(.presentRecipientDetails(userID: userID))
|
||||
case .displayReportRoom:
|
||||
actionsSubject.send(.presentReportRoomScreen)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -26,6 +26,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
|
||||
case displayMediaEventsTimeline
|
||||
case displayKnockingRequests
|
||||
case displaySecurityAndPrivacy
|
||||
case displayReportRoom
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
@@ -64,6 +65,8 @@ struct RoomDetailsScreenViewState: BindableState {
|
||||
var isKnockableRoom = false
|
||||
var knockRequestsCount = 0
|
||||
|
||||
var reportRoomEnabled = false
|
||||
|
||||
var canSeeKnockingRequests: Bool {
|
||||
knockingEnabled && dmRecipientInfo == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
|
||||
}
|
||||
@@ -218,6 +221,7 @@ enum RoomDetailsScreenViewAction {
|
||||
case processTapPinnedEvents
|
||||
case processTapMediaEvents
|
||||
case processTapRequestsToJoin
|
||||
case processTapReport
|
||||
}
|
||||
|
||||
enum RoomDetailsScreenViewShortcut {
|
||||
|
||||
@@ -78,6 +78,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
.weakAssign(to: \.state.knockingEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
appSettings.$reportRoomEnabled
|
||||
.weakAssign(to: \.state.reportRoomEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
appMediator.networkMonitor.reachabilityPublisher
|
||||
.filter { $0 == .reachable }
|
||||
.receive(on: DispatchQueue.main)
|
||||
@@ -174,6 +178,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
return
|
||||
}
|
||||
actionsSubject.send(.requestRecipientDetailsPresentation(userID: userID))
|
||||
case .processTapReport:
|
||||
actionsSubject.send(.displayReportRoom)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,9 +275,19 @@ struct RoomDetailsScreen: View {
|
||||
private var leaveRoomTitle: String {
|
||||
context.viewState.dmRecipientInfo == nil ? L10n.screenRoomDetailsLeaveRoomTitle : L10n.screenRoomDetailsLeaveConversationTitle
|
||||
}
|
||||
|
||||
private var reportRoomTitle: String {
|
||||
context.viewState.dmRecipientInfo == nil ? L10n.actionReportRoom : L10n.actionReport
|
||||
}
|
||||
|
||||
private var leaveRoomSection: some View {
|
||||
Section {
|
||||
if context.viewState.reportRoomEnabled {
|
||||
ListRow(label: .action(title: reportRoomTitle,
|
||||
icon: \.chatProblem,
|
||||
role: .destructive),
|
||||
kind: .button { context.send(viewAction: .processTapReport) })
|
||||
}
|
||||
ListRow(label: .action(title: leaveRoomTitle,
|
||||
icon: \.leave,
|
||||
role: .destructive),
|
||||
|
||||
@@ -53,7 +53,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
case .kickMember(let member):
|
||||
var reason: String?
|
||||
let binding: Binding<String> = .init(get: { reason ?? "" },
|
||||
set: { reason = $0.isEmpty ? nil : $0 })
|
||||
set: { reason = $0.isBlank ? nil : $0 })
|
||||
state.bindings.alertInfo = .init(id: .kickConfirmation,
|
||||
title: L10n.screenRoomMemberListKickMemberConfirmationTitle,
|
||||
message: L10n.screenRoomMemberListKickMemberConfirmationDescription,
|
||||
@@ -66,7 +66,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
case .banMember(let member):
|
||||
var reason: String?
|
||||
let binding: Binding<String> = .init(get: { reason ?? "" },
|
||||
set: { reason = $0.isEmpty ? nil : $0 })
|
||||
set: { reason = $0.isBlank ? nil : $0 })
|
||||
state.bindings.alertInfo = .init(id: .banConfirmation,
|
||||
title: L10n.screenRoomMemberListBanMemberConfirmationTitle,
|
||||
message: L10n.screenRoomMemberListBanMemberConfirmationDescription,
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
||||
var enableOnlySignedDeviceIsolationMode: Bool { get set }
|
||||
var elementCallBaseURLOverride: URL? { get set }
|
||||
var knockingEnabled: Bool { get set }
|
||||
var reportRoomEnabled: Bool { get set }
|
||||
}
|
||||
|
||||
extension AppSettings: DeveloperOptionsProtocol { }
|
||||
|
||||
+7
@@ -62,6 +62,13 @@ struct DeveloperOptionsScreen: View {
|
||||
} footer: {
|
||||
Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.")
|
||||
}
|
||||
|
||||
Section("Reporting") {
|
||||
Toggle(isOn: $context.reportRoomEnabled) {
|
||||
Text("Report rooms")
|
||||
Text("Report API might not work properly")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
|
||||
|
||||
@@ -255,6 +255,16 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError> {
|
||||
do {
|
||||
try await room.reportRoom(reason: reason)
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed reporting room: \(id) with error: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
}
|
||||
}
|
||||
|
||||
func updateMembers() async {
|
||||
// We always update members first using the no sync API in case internet is not readily available
|
||||
// To get the members stored on disk first, this API call is very fast.
|
||||
|
||||
@@ -92,6 +92,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user