From 94c34e03ddc44fe13388f7ad94b054a5665acadf Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 14 Jan 2023 11:03:39 -0500 Subject: [PATCH] Add reporting accounts and statuses --- .../Pachyderm/Sources/Pachyderm/Client.swift | 17 +- .../Sources/Pachyderm/Model/Account.swift | 5 +- .../Sources/Pachyderm/Model/Instance.swift | 10 + Tusker.xcodeproj/project.pbxproj | 28 +++ Tusker/Models/EditedReport.swift | 43 +++++ Tusker/Screens/Mute/MuteAccountView.swift | 3 +- .../Screens/Report/ReportAddStatusView.swift | 58 ++++++ .../Report/ReportSelectRulesView.swift | 59 ++++++ Tusker/Screens/Report/ReportStatusView.swift | 38 ++++ Tusker/Screens/Report/ReportView.swift | 172 ++++++++++++++++++ Tusker/Screens/Utilities/Previewing.swift | 15 +- 11 files changed, 440 insertions(+), 8 deletions(-) create mode 100644 Tusker/Models/EditedReport.swift create mode 100644 Tusker/Screens/Report/ReportAddStatusView.swift create mode 100644 Tusker/Screens/Report/ReportSelectRulesView.swift create mode 100644 Tusker/Screens/Report/ReportStatusView.swift create mode 100644 Tusker/Screens/Report/ReportView.swift diff --git a/Packages/Pachyderm/Sources/Pachyderm/Client.swift b/Packages/Pachyderm/Sources/Pachyderm/Client.swift index 85d4e4b0..65ba2958 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Client.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Client.swift @@ -323,11 +323,20 @@ public class Client { return Request<[Report]>(method: .get, path: "/api/v1/reports") } - public static func report(account: Account, statuses: [Status], comment: String) -> Request { + public static func report( + account: String, + statuses: [String], + comment: String, + forward: Bool, + category: String, + ruleIDs: [String] + ) -> Request { return Request(method: .post, path: "/api/v1/reports", body: ParametersBody([ - "account_id" => account.id, - "comment" => comment - ] + "status_ids" => statuses.map { $0.id })) + "account_id" => account, + "comment" => comment, + "forward" => forward, + "category" => category, + ] + "status_ids" => statuses + "rule_ids" => ruleIDs)) } // MARK: - Search diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Account.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Account.swift index 053e1a05..777ab19e 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Account.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Account.swift @@ -94,11 +94,12 @@ public final class Account: AccountProtocol, Decodable { return request } - public static func getStatuses(_ accountID: String, range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil) -> Request<[Status]> { + public static func getStatuses(_ accountID: String, range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil, excludeReblogs: Bool? = nil) -> Request<[Status]> { var request = Request<[Status]>(method: .get, path: "/api/v1/accounts/\(accountID)/statuses", queryParameters: [ "only_media" => onlyMedia, "pinned" => pinned, - "exclude_replies" => excludeReplies + "exclude_replies" => excludeReplies, + "exclude_reblogs" => excludeReblogs, ]) request.range = range return request diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Instance.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Instance.swift index 407db5c3..10a0ab4b 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Instance.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Instance.swift @@ -20,6 +20,7 @@ public class Instance: Decodable { public let languages: [String]? public let stats: Stats? public let configuration: Configuration? + public let rules: [Rule]? // pleroma doesn't currently implement these public let contactAccount: Account? @@ -57,6 +58,7 @@ public class Instance: Decodable { self.thumbnail = try? container.decodeIfPresent(URL.self, forKey: .thumbnail) self.configuration = try? container.decodeIfPresent(Configuration.self, forKey: .configuration) + self.rules = try? container.decodeIfPresent([Rule].self, forKey: .rules) if let maxTootCharacters = try? container.decodeIfPresent(Int.self, forKey: .maxTootCharacters) { self.maxTootCharacters = maxTootCharacters @@ -83,6 +85,7 @@ public class Instance: Decodable { case stats case configuration case contactAccount = "contact_account" + case rules case maxTootCharacters = "max_toot_chars" case pollLimits = "poll_limits" @@ -167,3 +170,10 @@ extension Instance { } } } + +extension Instance { + public struct Rule: Decodable, Identifiable { + public let id: String + public let text: String + } +} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index f029f6a3..9b3812b7 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -145,6 +145,11 @@ D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; productRef = D6552366289870790048A653 /* ScreenCorners */; }; D659F35E2953A212002D944A /* TTTKit in Frameworks */ = {isa = PBXBuildFile; productRef = D659F35D2953A212002D944A /* TTTKit */; }; D659F36229541065002D944A /* TTTView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D659F36129541065002D944A /* TTTView.swift */; }; + D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B532971F71D00DABDFB /* EditedReport.swift */; }; + D65B4B562971F98300DABDFB /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B552971F98300DABDFB /* ReportView.swift */; }; + D65B4B58297203A700DABDFB /* ReportSelectRulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B57297203A700DABDFB /* ReportSelectRulesView.swift */; }; + D65B4B5A29720AB000DABDFB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B5929720AB000DABDFB /* ReportStatusView.swift */; }; + D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B5D2973040D00DABDFB /* ReportAddStatusView.swift */; }; D65C6BF525478A9C00A6E89C /* BackgroundableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */; }; D65F613423AEAB6600F3CFD3 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */; }; D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; }; @@ -527,6 +532,11 @@ D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = ""; }; D653F410267D1E32004E32B1 /* DiffableTimelineLikeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableTimelineLikeTableViewController.swift; sourceTree = ""; }; D659F36129541065002D944A /* TTTView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTTView.swift; sourceTree = ""; }; + D65B4B532971F71D00DABDFB /* EditedReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditedReport.swift; sourceTree = ""; }; + D65B4B552971F98300DABDFB /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = ""; }; + D65B4B57297203A700DABDFB /* ReportSelectRulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSelectRulesView.swift; sourceTree = ""; }; + D65B4B5929720AB000DABDFB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = ""; }; + D65B4B5D2973040D00DABDFB /* ReportAddStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportAddStatusView.swift; sourceTree = ""; }; D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundableViewController.swift; sourceTree = ""; }; D65F612D23AE990C00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D65F613023AE99E000F3CFD3 /* Ambassador.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ambassador.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -810,6 +820,7 @@ D677284D24ECC01D00C732D3 /* Draft.swift */, D627FF75217E923E00CC0648 /* DraftsManager.swift */, D61F75AE293AF50C00C0B37F /* EditedFilter.swift */, + D65B4B532971F71D00DABDFB /* EditedReport.swift */, ); path = Models; sourceTree = ""; @@ -963,6 +974,7 @@ D641C783213DD7FE004B4513 /* Onboarding */, D641C789213DD87E004B4513 /* Preferences */, D641C784213DD819004B4513 /* Profile */, + D65B4B522971F6E300DABDFB /* Report */, D6BC9DD8232D8BCA002CA326 /* Search */, D6A3BC8C2321FF9B00FD64D5 /* Status Action Account List */, D641C781213DD7DD004B4513 /* Timeline */, @@ -1173,6 +1185,17 @@ name = Frameworks; sourceTree = ""; }; + D65B4B522971F6E300DABDFB /* Report */ = { + isa = PBXGroup; + children = ( + D65B4B552971F98300DABDFB /* ReportView.swift */, + D65B4B57297203A700DABDFB /* ReportSelectRulesView.swift */, + D65B4B5929720AB000DABDFB /* ReportStatusView.swift */, + D65B4B5D2973040D00DABDFB /* ReportAddStatusView.swift */, + ); + path = Report; + sourceTree = ""; + }; D663626021360A9600C9CBA2 /* Preferences */ = { isa = PBXGroup; children = ( @@ -1975,6 +1998,7 @@ D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */, + D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */, D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */, D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */, D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */, @@ -2020,6 +2044,7 @@ D6BEA24B291C6A2B002F4D01 /* AlertWithData.swift in Sources */, D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */, D61F75AB293AF11400C0B37F /* FilterKeywordMO.swift in Sources */, + D65B4B5A29720AB000DABDFB /* ReportStatusView.swift in Sources */, D663626221360B1900C9CBA2 /* Preferences.swift in Sources */, D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */, D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */, @@ -2065,7 +2090,9 @@ D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */, D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */, D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */, + D65B4B58297203A700DABDFB /* ReportSelectRulesView.swift in Sources */, D6B81F442560390300F6E31D /* MenuController.swift in Sources */, + D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */, D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */, D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */, D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */, @@ -2127,6 +2154,7 @@ D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */, D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */, D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */, + D65B4B562971F98300DABDFB /* ReportView.swift in Sources */, D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */, D68A76E829527884001DA1B3 /* PinnedTimelinesView.swift in Sources */, D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */, diff --git a/Tusker/Models/EditedReport.swift b/Tusker/Models/EditedReport.swift new file mode 100644 index 00000000..e5262006 --- /dev/null +++ b/Tusker/Models/EditedReport.swift @@ -0,0 +1,43 @@ +// +// EditedReport.swift +// Tusker +// +// Created by Shadowfacts on 1/13/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import Foundation +import Pachyderm + +class EditedReport: ObservableObject { + let accountID: String + @Published var statusIDs = [String]() + @Published var comment = "" + @Published var forward = false + @Published var reason: Reason = .spam + + init(accountID: String) { + self.accountID = accountID + } + + func makeRequest() -> Request? { + let category: String + let ruleIDs: [String] + switch reason { + case .spam: + category = "spam" + ruleIDs = [] + case .rules(let ids): + category = "violation" + ruleIDs = ids + } + return Client.report(account: accountID, statuses: statusIDs, comment: comment, forward: forward, category: category, ruleIDs: ruleIDs) + } +} + +extension EditedReport { + enum Reason { + case spam + case rules([String]) + } +} diff --git a/Tusker/Screens/Mute/MuteAccountView.swift b/Tusker/Screens/Mute/MuteAccountView.swift index fedd6a65..6fde9da3 100644 --- a/Tusker/Screens/Mute/MuteAccountView.swift +++ b/Tusker/Screens/Mute/MuteAccountView.swift @@ -69,7 +69,8 @@ struct MuteAccountView: View { } .frame(height: 50) .listRowBackground(EmptyView()) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + // vertical insets are so the rounded corners fo the section don't affect the avatar + .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0)) } .accessibilityHidden(true) diff --git a/Tusker/Screens/Report/ReportAddStatusView.swift b/Tusker/Screens/Report/ReportAddStatusView.swift new file mode 100644 index 00000000..07140a3a --- /dev/null +++ b/Tusker/Screens/Report/ReportAddStatusView.swift @@ -0,0 +1,58 @@ +// +// ReportAddStatusView.swift +// Tusker +// +// Created by Shadowfacts on 1/14/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import SwiftUI +import Pachyderm + +struct ReportAddStatusView: View { + @ObservedObject var report: EditedReport + let mastodonController: MastodonController + + @Environment(\.dismiss) private var dismiss + @State private var statuses: [StatusMO]? + @State private var error: Error? + + var body: some View { + statusesListIfLoaded + .navigationTitle("Add Posts") + } + + @ViewBuilder + private var statusesListIfLoaded: some View { + if let statuses { + List { + ForEach(statuses, id: \.id) { status in + Button { + report.statusIDs.append(status.id) + dismiss() + } label: { + ReportStatusView(status: status, mastodonController: mastodonController) + } + } + } + } else { + ProgressView() + .progressViewStyle(.circular) + .alertWithData("Error Loading Posts", data: $error, actions: { _ in + Button("OK") {} + }, message: { error in + Text(error.localizedDescription) + }) + .task { @MainActor in + do { + let req = Account.getStatuses(report.accountID, excludeReplies: false, excludeReblogs: true) + let (statuses, _) = try await mastodonController.run(req) + await mastodonController.persistentContainer.addAll(statuses: statuses) + self.statuses = statuses.compactMap { mastodonController.persistentContainer.status(for: $0.id) } + } catch { + self.error = error + } + } + } + } +} diff --git a/Tusker/Screens/Report/ReportSelectRulesView.swift b/Tusker/Screens/Report/ReportSelectRulesView.swift new file mode 100644 index 00000000..bfeb5a7a --- /dev/null +++ b/Tusker/Screens/Report/ReportSelectRulesView.swift @@ -0,0 +1,59 @@ +// +// ReportSelectRulesView.swift +// Tusker +// +// Created by Shadowfacts on 1/13/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import SwiftUI + +struct ReportSelectRulesView: View { + @ObservedObject var mastodonController: MastodonController + @ObservedObject var report: EditedReport + + var selectedRuleIDs: [String] { + get { + if case .rules(let ids) = report.reason { + return ids + } else { + return [] + } + } + nonmutating set { + report.reason = .rules(newValue) + } + } + + init(mastodonController: MastodonController, report: EditedReport) { + self.mastodonController = mastodonController + self.report = report + } + + var body: some View { + List(mastodonController.instance.rules!) { rule in + Button { + if selectedRuleIDs.contains(rule.id) { + selectedRuleIDs.removeAll(where: { $0 == rule.id }) + } else { + selectedRuleIDs.append(rule.id) + } + } label: { + HStack { + Text(rule.text) + .foregroundColor(.primary) + Spacer() + Image(systemName: "checkmark") + .foregroundColor(selectedRuleIDs.contains(rule.id) ? .accentColor : .clear) + } + } + } + .navigationTitle("Rules") + } +} + +//struct ReportSelectRulesView_Previews: PreviewProvider { +// static var previews: some View { +// ReportSelectRulesView() +// } +//} diff --git a/Tusker/Screens/Report/ReportStatusView.swift b/Tusker/Screens/Report/ReportStatusView.swift new file mode 100644 index 00000000..c9410f9e --- /dev/null +++ b/Tusker/Screens/Report/ReportStatusView.swift @@ -0,0 +1,38 @@ +// +// ReportStatusView.swift +// Tusker +// +// Created by Shadowfacts on 1/13/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import SwiftUI +import SwiftSoup + +private var converter = HTMLConverter() + +struct ReportStatusView: View { + let status: StatusMO + let mastodonController: MastodonController + + private var text: AttributedString { + let str = AttributedString(converter.convert(status.content)) + return str.transformingAttributes(\.link) { transformer in + if transformer.value != nil { + transformer.replace(with: \.foregroundColor, value: .accentColor) + } + } + } + + var body: some View { + VStack(alignment: .leading) { + Text(text) + + if !status.attachments.isEmpty { + Text("^[\(status.attachments.count) attachments](inflect: true)") + .foregroundColor(.secondary) + .font(.caption.bold()) + } + } + } +} diff --git a/Tusker/Screens/Report/ReportView.swift b/Tusker/Screens/Report/ReportView.swift new file mode 100644 index 00000000..095c64d0 --- /dev/null +++ b/Tusker/Screens/Report/ReportView.swift @@ -0,0 +1,172 @@ +// +// ReportView.swift +// Tusker +// +// Created by Shadowfacts on 1/13/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import SwiftUI + +struct ReportView: View { + + let account: AccountMO + @ObservedObject var mastodonController: MastodonController + @StateObject var report: EditedReport + + @Environment(\.dismiss) private var dismiss + @ObservedObject private var preferences = Preferences.shared + @State private var isReporting = false + @State private var error: Error? + + init(report: EditedReport, mastodonController: MastodonController) { + self.account = mastodonController.persistentContainer.account(for: report.accountID)! + self.mastodonController = mastodonController + self._report = StateObject(wrappedValue: report) + if mastodonController.instance.rules == nil { + report.reason = .spam + } + } + + var body: some View { + if #available(iOS 16.0, *) { + NavigationStack { + navigationViewContent + } + } else { + NavigationView { + navigationViewContent + } + .navigationViewStyle(.stack) + } + } + + private var navigationViewContent: some View { + Form { + Section { + HStack { + ComposeAvatarImageView(url: account.avatar) + .frame(width: 50, height: 50) + .cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50) + + VStack(alignment: .leading) { + AccountDisplayNameLabel(account: account, textStyle: .headline, emojiSize: 17) + Text("@\(account.acct)") + .fontWeight(.light) + .foregroundColor(.secondary) + } + } + .frame(height: 50) + .listRowBackground(EmptyView()) + // vertical insets are so the rounded corners fo the section don't affect the avatar + .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0)) + } + .accessibilityHidden(true) + + Section { + Button { + report.reason = .spam + } label: { + HStack { + Text("Spam") + Spacer() + if case .spam = report.reason { + Image(systemName: "checkmark") + } + } + } + + if mastodonController.instance.rules != nil { + NavigationLink { + ReportSelectRulesView(mastodonController: mastodonController, report: report) + } label: { + HStack { + Text("Instance Rule") + Spacer() + if case .rules(_) = report.reason { + Image(systemName: "checkmark") + } + } + .foregroundColor(.accentColor) + } + } + } header: { + Text("Reason") + } + + Section { + ComposeTextView(text: $report.comment, placeholder: Text("Add any additional comments")) + .backgroundColor(.clear) + .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) + } + + Section { + ForEach(report.statusIDs, id: \.self) { id in + ReportStatusView(status: mastodonController.persistentContainer.status(for: id)!, mastodonController: mastodonController) + } + .onDelete { indices in + report.statusIDs.remove(atOffsets: indices) + } + NavigationLink { + ReportAddStatusView(report: report, mastodonController: mastodonController) + } label: { + Label("Add Posts…", systemImage: "plus") + .foregroundColor(.accentColor) + } + } footer: { + Text("Attach posts to your report to provide additional context for moderators.") + } + + Section { + Toggle("Forward", isOn: $report.forward) + } footer: { + Text("You can choose to anonymously forward your report to the moderators of **\(account.url.host!)**.") + } + + Button(action: self.sendReport) { + if isReporting { + Text("Sending Report") + Spacer() + ProgressView() + .progressViewStyle(.circular) + } else { + Text("Send Report") + } + } + .disabled(isReporting) + } + .alertWithData("Error Reporting", data: $error, actions: { error in + Button("OK") {} + }, message: { error in + Text(error.localizedDescription) + }) + .navigationTitle("Report \(account.displayOrUserName)") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + self.dismiss() + } + } + } + } + + private func sendReport() { + isReporting = true + Task { + do { + _ = try await mastodonController.run(report.makeRequest()!) + self.dismiss() + } catch { + self.error = error + self.isReporting = false + } + } + } +} + +//struct ReportView_Previews: PreviewProvider { +// static var previews: some View { +// ReportView() +// } +//} diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 3ba68f32..6d35bee4 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -84,6 +84,11 @@ extension MenuActionProvider { })) suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) })) suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.muteAction(for: $0, mastodonController: $1) })) + suppressSection.append(createAction(identifier: "report", title: "Report", systemImageName: "flag", handler: { [unowned self] _ in + let view = ReportView(report: EditedReport(accountID: accountID), mastodonController: mastodonController) + let host = UIHostingController(rootView: view) + self.navigationDelegate?.present(host, animated: true) + })) } addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showProfileActivity(id: accountID, accountID: loggedInAccountID)) @@ -210,7 +215,15 @@ extension MenuActionProvider { }), at: 1) } - var actionsSection: [UIAction] = [] + var actionsSection: [UIAction] = [ + createAction(identifier: "report", title: "Report", systemImageName: "flag", handler: { [weak self] _ in + let report = EditedReport(accountID: status.account.id) + report.statusIDs = [status.id] + let view = ReportView(report: report, mastodonController: mastodonController) + let host = UIHostingController(rootView: view) + self?.navigationDelegate?.present(host, animated: true) + }) + ] if includeStatusButtonActions { actionsSection.insert(createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { [weak self] (_) in