Report Content (#587)

* ui for reporting content has been implemented

* implementation done, will only work withe a mx rust sdk that supports report content

* fix

* code improvements

* code improvement

* fix typo

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>

* removing unneeded task

* code improvement

* changelog

---------

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
Mauro
2023-02-22 18:14:43 +01:00
committed by GitHub
parent 1e8d9fd6b2
commit 5d5f788a77
12 changed files with 127 additions and 1 deletions

View File

@@ -0,0 +1,45 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
protocol AlertItem {
var title: String { get }
}
extension View {
func alert<I, V>(item: Binding<I?>, actions: (I) -> V, message: (I) -> V) -> some View where I: AlertItem, V: View {
let binding = Binding<Bool>(get: {
item.wrappedValue != nil
}, set: { newValue in
if !newValue {
item.wrappedValue = nil
}
})
return alert(item.wrappedValue?.title ?? "", isPresented: binding, presenting: item.wrappedValue, actions: actions, message: message)
}
func alert<I, V>(item: Binding<I?>, actions: (I) -> V) -> some View where I: AlertItem, V: View {
let binding = Binding<Bool>(get: {
item.wrappedValue != nil
}, set: { newValue in
if !newValue {
item.wrappedValue = nil
}
})
return alert(item.wrappedValue?.title ?? "", isPresented: binding, presenting: item.wrappedValue, actions: actions)
}
}

View File

@@ -15,6 +15,7 @@
//
import Combine
import SwiftUI
import UIKit
enum RoomScreenViewModelAction {
@@ -54,6 +55,7 @@ enum RoomScreenViewAction {
/// Mark the entire room as read - this is heavy handed as a starting point for now.
case markRoomAsRead
case contextMenuAction(itemID: String, action: TimelineItemContextMenuAction)
case report(itemID: String, reason: String)
}
struct RoomScreenViewState: BindableState {
@@ -87,8 +89,27 @@ struct RoomScreenViewStateBindings {
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<RoomScreenErrorType>?
var debugInfo: TimelineItemDebugView.DebugInfo?
// Report
var report: ReportAlertItem?
}
final class ReportAlertItem: AlertItem {
init(itemID: String) {
self.itemID = itemID
}
let title = ElementL10n.reportContentCustomHint
let itemID: String
private(set) var reason = ""
lazy var reasonBinding = Binding<String>(get: { [unowned self] in
self.reason
}, set: { [unowned self] newValue in
self.reason = newValue
})
}
enum RoomScreenErrorType: Hashable {

View File

@@ -120,6 +120,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
await markRoomAsRead()
case .contextMenuAction(let itemID, let action):
processContentMenuAction(action, itemID: itemID)
case let .report(itemID, reason):
await timelineController.reportContent(itemID, reason: reason)
}
}
@@ -227,6 +229,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
if item.isOutgoing {
actions.append(.redact)
} else {
actions.append(.report)
}
var debugActions: [TimelineItemContextMenuAction] = ServiceLocator.shared.settings.canShowDeveloperOptions ? [.viewSource] : []
@@ -280,6 +284,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
Task {
await timelineController.retryDecryption(for: sessionID)
}
case .report:
state.bindings.report = ReportAlertItem(itemID: itemID)
}
if action.switchToDefaultComposer {

View File

@@ -30,6 +30,8 @@ struct RoomScreen: View {
.toolbarBackground(.visible, for: .navigationBar) // Fix the toolbar's background.
.overlay { loadingIndicator }
.alert(item: $context.alertInfo) { $0.alert }
.alert(item: $context.report,
actions: { reportAlertActions($0) })
.sheet(item: $context.debugInfo) { TimelineItemDebugView(info: $0) }
.task(id: context.viewState.roomId) {
// Give a couple of seconds for items to load and to see them.
@@ -39,6 +41,15 @@ struct RoomScreen: View {
context.send(viewAction: .markRoomAsRead)
}
}
@ViewBuilder
func reportAlertActions(_ report: ReportAlertItem) -> some View {
TextField("", text: report.reasonBinding)
Button(ElementL10n.actionSend, action: {
context.send(viewAction: .report(itemID: report.itemID, reason: report.reason))
})
Button(ElementL10n.actionCancel, role: .cancel, action: { })
}
var timeline: some View {
TimelineView()

View File

@@ -40,6 +40,7 @@ enum TimelineItemContextMenuAction: Identifiable, Hashable {
case reply
case viewSource
case retryDecryption(sessionID: String)
case report
var id: Self { self }
@@ -107,6 +108,10 @@ public struct TimelineItemContextMenu: View {
Button { send(action) } label: {
Label(ElementL10n.roomTimelineContextMenuRetryDecryption, systemImage: "arrow.down.message")
}
case .report:
Button(role: .destructive) { send(action) } label: {
Label(ElementL10n.reportContent, systemImage: "exclamationmark.bubble")
}
}
}
}

View File

@@ -74,6 +74,10 @@ struct MockRoomProxy: RoomProxyProtocol {
.failure(.failedRedactingEvent)
}
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> {
.failure(.failedReportingContent)
}
func members() async -> Result<[RoomMemberProxy], RoomProxyError> {
if let members {
return .success(members)

View File

@@ -253,6 +253,22 @@ class RoomProxy: RoomProxyProtocol {
}
}
}
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: userInitiatedDispatchQueue) {
do {
try self.room.reportContent(eventId: eventID, score: nil, reason: reason)
return .success(())
} catch {
return .failure(.failedReportingContent)
}
}
}
func members() async -> Result<[RoomMemberProxy], RoomProxyError> {
await Task.dispatch(on: .global()) {

View File

@@ -28,6 +28,7 @@ enum RoomProxyError: Error {
case failedSendingReaction
case failedEditingMessage
case failedRedactingEvent
case failedReportingContent
case failedAddingTimelineListener
case failedRetrievingMembers
}
@@ -71,6 +72,8 @@ protocol RoomProxyProtocol {
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func members() async -> Result<[RoomMemberProxy], RoomProxyError>
func retryDecryption(for sessionID: String) async

View File

@@ -64,6 +64,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func editMessage(_ newMessage: String, original itemID: String) async { }
func redact(_ itemID: String) async { }
func reportContent(_ itemID: String, reason: String?) async { }
func debugDescription(for itemID: String) -> String {
"Mock debug description"

View File

@@ -187,6 +187,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
MXLog.error("Failed redacting message with error: \(error)")
}
}
func reportContent(_ itemID: String, reason: String?) async {
MXLog.info("Send report content in \(roomID)")
switch await roomProxy.reportContent(itemID, reason: reason) {
case .success:
MXLog.info("Finished reporting content")
case .failure(let error):
MXLog.error("Failed reporting content with error: \(error)")
}
}
// Handle this parallel to the timeline items so we're not forced
// to bundle the Rust side objects within them

View File

@@ -59,6 +59,8 @@ protocol RoomTimelineControllerProtocol {
func sendReaction(_ reaction: String, to itemID: String) async
func redact(_ itemID: String) async
func reportContent(_ itemID: String, reason: String?) async
func debugDescription(for itemID: String) -> String

1
changelog.d/115.feature Normal file
View File

@@ -0,0 +1 @@
Added a feature that allows a user to report content posted by another user by opening the context menu and provide a reason.