mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-28 19:11:30 +00:00
Reporting
This commit is contained in:
parent
6e7541f208
commit
579f64f089
16 changed files with 379 additions and 13 deletions
|
@ -114,6 +114,12 @@
|
||||||
"notifications.poll-ended" = "A poll you have voted in has ended";
|
"notifications.poll-ended" = "A poll you have voted in has ended";
|
||||||
"notifications.your-poll-ended" = "Your poll has ended";
|
"notifications.your-poll-ended" = "Your poll has ended";
|
||||||
"notifications.unknown" = "Notification from %@";
|
"notifications.unknown" = "Notification from %@";
|
||||||
|
"report" = "Report";
|
||||||
|
"report.hint" = "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:";
|
||||||
|
"report.placeholder" = "Additional comments";
|
||||||
|
"report.target-%@" = "Reporting %@";
|
||||||
|
"report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?";
|
||||||
|
"report.forward-%@" = "Forward report to %@";
|
||||||
"status.reblogged-by" = "%@ boosted";
|
"status.reblogged-by" = "%@ boosted";
|
||||||
"status.pinned-post" = "Pinned post";
|
"status.pinned-post" = "Pinned post";
|
||||||
"status.show-more" = "Show More";
|
"status.show-more" = "Show More";
|
||||||
|
@ -125,6 +131,7 @@
|
||||||
"status.visibility.public" = "Public";
|
"status.visibility.public" = "Public";
|
||||||
"status.visibility.unlisted" = "Unlisted";
|
"status.visibility.unlisted" = "Unlisted";
|
||||||
"status.visibility.private" = "Private";
|
"status.visibility.private" = "Private";
|
||||||
|
"submit" = "Submit";
|
||||||
"timelines.home" = "Home";
|
"timelines.home" = "Home";
|
||||||
"timelines.local" = "Local";
|
"timelines.local" = "Local";
|
||||||
"timelines.federated" = "Federated";
|
"timelines.federated" = "Federated";
|
||||||
|
|
12
Mastodon/Sources/Mastodon/Entities/Report.swift
Normal file
12
Mastodon/Sources/Mastodon/Entities/Report.swift
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Report: Codable, Hashable {
|
||||||
|
public let id: Id
|
||||||
|
public let actionTaken: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Report {
|
||||||
|
typealias Id = String
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import HTTP
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
public enum ReportEndpoint {
|
||||||
|
case create(Elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ReportEndpoint {
|
||||||
|
struct Elements {
|
||||||
|
public let accountId: Account.Id
|
||||||
|
public var statusIds = Set<Status.Id>()
|
||||||
|
public var comment = ""
|
||||||
|
public var forward = false
|
||||||
|
|
||||||
|
public init(accountId: Account.Id) {
|
||||||
|
self.accountId = accountId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ReportEndpoint: Endpoint {
|
||||||
|
public typealias ResultType = Report
|
||||||
|
|
||||||
|
public var context: [String] {
|
||||||
|
defaultContext + ["reports"]
|
||||||
|
}
|
||||||
|
|
||||||
|
public var pathComponentsInContext: [String] {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
public var jsonBody: [String: Any]? {
|
||||||
|
switch self {
|
||||||
|
case let .create(creation):
|
||||||
|
var params: [String: Any] = ["account_id": creation.accountId]
|
||||||
|
|
||||||
|
if !creation.statusIds.isEmpty {
|
||||||
|
params["status_ids"] = Array(creation.statusIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !creation.comment.isEmpty {
|
||||||
|
params["comment"] = creation.comment
|
||||||
|
}
|
||||||
|
|
||||||
|
if creation.forward {
|
||||||
|
params["forward"] = creation.forward
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var method: HTTPMethod {
|
||||||
|
switch self {
|
||||||
|
case .create:
|
||||||
|
return .post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@
|
||||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D812544D80000B1EBEF /* PollOptionButton.swift */; };
|
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D812544D80000B1EBEF /* PollOptionButton.swift */; };
|
||||||
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; };
|
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; };
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||||
|
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||||
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; };
|
D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */; };
|
||||||
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8510B25259E56004E0744 /* LoadMoreCell.swift */; };
|
D0B8510C25259E56004E0744 /* LoadMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8510B25259E56004E0744 /* LoadMoreCell.swift */; };
|
||||||
|
@ -71,6 +72,7 @@
|
||||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
||||||
|
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD50CA256B1F24004A04F7 /* ReportView.swift */; };
|
||||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */; };
|
||||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||||
|
@ -156,6 +158,7 @@
|
||||||
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionButton.swift; sourceTree = "<group>"; };
|
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionButton.swift; sourceTree = "<group>"; };
|
||||||
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = "<group>"; };
|
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = "<group>"; };
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||||
|
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||||
D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
|
D0B32F4F250B373600311912 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
|
||||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCollection+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -193,6 +196,7 @@
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
D0D7C013250440610039AD6F /* CodableBloomFilter */ = {isa = PBXFileReference; lastKnownFileType = folder; path = CodableBloomFilter; sourceTree = "<group>"; };
|
||||||
|
D0DD50CA256B1F24004A04F7 /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = "<group>"; };
|
||||||
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
||||||
D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebfingerIndicatorView.swift; sourceTree = "<group>"; };
|
D0E1F582251F13EC00D45315 /* WebfingerIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebfingerIndicatorView.swift; sourceTree = "<group>"; };
|
||||||
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
||||||
|
@ -289,10 +293,11 @@
|
||||||
D0EA593F2522AC8700804347 /* CardView.swift */,
|
D0EA593F2522AC8700804347 /* CardView.swift */,
|
||||||
D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */,
|
D01F41E224F8889700D55A2D /* StatusAttachmentsView.swift */,
|
||||||
D0BEB1F224F8EE8C001B0F04 /* StatusAttachmentView.swift */,
|
D0BEB1F224F8EE8C001B0F04 /* StatusAttachmentView.swift */,
|
||||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */,
|
|
||||||
D036AA16254CA823009094DF /* StatusBodyView.swift */,
|
D036AA16254CA823009094DF /* StatusBodyView.swift */,
|
||||||
|
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */,
|
||||||
D0625E58250F092900502611 /* StatusListCell.swift */,
|
D0625E58250F092900502611 /* StatusListCell.swift */,
|
||||||
D00CB2EC2533ACC00080096B /* StatusView.swift */,
|
D00CB2EC2533ACC00080096B /* StatusView.swift */,
|
||||||
|
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */,
|
||||||
);
|
);
|
||||||
path = Status;
|
path = Status;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -374,6 +379,7 @@
|
||||||
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */,
|
D0C7D42824F76169001EBDBB /* PostingReadingPreferencesView.swift */,
|
||||||
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
||||||
D0B32F4F250B373600311912 /* RegistrationView.swift */,
|
D0B32F4F250B373600311912 /* RegistrationView.swift */,
|
||||||
|
D0DD50CA256B1F24004A04F7 /* ReportView.swift */,
|
||||||
D0C7D42724F76169001EBDBB /* RootView.swift */,
|
D0C7D42724F76169001EBDBB /* RootView.swift */,
|
||||||
D02E1F94250B13210071AD56 /* SafariView.swift */,
|
D02E1F94250B13210071AD56 /* SafariView.swift */,
|
||||||
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */,
|
D0C7D42924F76169001EBDBB /* SecondaryNavigationView.swift */,
|
||||||
|
@ -637,6 +643,7 @@
|
||||||
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */,
|
||||||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
|
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||||
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */,
|
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */,
|
||||||
|
@ -683,6 +690,7 @@
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */,
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||||
|
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */,
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||||
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */,
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import MastodonAPI
|
||||||
|
|
||||||
|
public typealias ReportElements = ReportEndpoint.Elements
|
|
@ -15,11 +15,11 @@ public struct AccountService {
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let contentDatabase: ContentDatabase
|
private let contentDatabase: ContentDatabase
|
||||||
|
|
||||||
init(account: Account,
|
public init(account: Account,
|
||||||
relationship: Relationship? = nil,
|
relationship: Relationship? = nil,
|
||||||
identityProofs: [IdentityProof] = [],
|
identityProofs: [IdentityProof] = [],
|
||||||
mastodonAPIClient: MastodonAPIClient,
|
mastodonAPIClient: MastodonAPIClient,
|
||||||
contentDatabase: ContentDatabase) {
|
contentDatabase: ContentDatabase) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.relationship = relationship
|
self.relationship = relationship
|
||||||
self.identityProofs = identityProofs
|
self.identityProofs = identityProofs
|
||||||
|
@ -30,6 +30,10 @@ public struct AccountService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AccountService {
|
public extension AccountService {
|
||||||
|
var isLocal: Bool {
|
||||||
|
account.url.host == mastodonAPIClient.instanceURL.host
|
||||||
|
}
|
||||||
|
|
||||||
func follow() -> AnyPublisher<Never, Error> {
|
func follow() -> AnyPublisher<Never, Error> {
|
||||||
relationshipAction(.accountsFollow(id: account.id))
|
relationshipAction(.accountsFollow(id: account.id))
|
||||||
}
|
}
|
||||||
|
@ -82,6 +86,10 @@ public extension AccountService {
|
||||||
func set(note: String) -> AnyPublisher<Never, Error> {
|
func set(note: String) -> AnyPublisher<Never, Error> {
|
||||||
relationshipAction(.note(note, id: account.id))
|
relationshipAction(.note(note, id: account.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func report(_ elements: ReportElements) -> AnyPublisher<Never, Error> {
|
||||||
|
mastodonAPIClient.request(ReportEndpoint.create(elements)).ignoreOutput().eraseToAnyPublisher()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AccountService {
|
private extension AccountService {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import UIKit
|
import SwiftUI
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
final class ProfileViewController: TableViewController {
|
final class ProfileViewController: TableViewController {
|
||||||
|
@ -66,6 +66,7 @@ final class ProfileViewController: TableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ProfileViewController {
|
private extension ProfileViewController {
|
||||||
|
// swiftlint:disable:next function_body_length
|
||||||
func menu(accountViewModel: AccountViewModel, relationship: Relationship) -> UIMenu {
|
func menu(accountViewModel: AccountViewModel, relationship: Relationship) -> UIMenu {
|
||||||
var actions = [UIAction]()
|
var actions = [UIAction]()
|
||||||
|
|
||||||
|
@ -99,6 +100,17 @@ private extension ProfileViewController {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actions.append(UIAction(
|
||||||
|
title: NSLocalizedString("report", comment: ""),
|
||||||
|
image: UIImage(systemName: "flag"),
|
||||||
|
attributes: .destructive) { [weak self] _ in
|
||||||
|
guard let self = self,
|
||||||
|
let reportViewModel = self.viewModel.accountViewModel?.reportViewModel()
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.report(viewModel: reportViewModel)
|
||||||
|
})
|
||||||
|
|
||||||
if relationship.blocking {
|
if relationship.blocking {
|
||||||
actions.append(UIAction(
|
actions.append(UIAction(
|
||||||
title: NSLocalizedString("account.unblock", comment: ""),
|
title: NSLocalizedString("account.unblock", comment: ""),
|
||||||
|
|
|
@ -126,6 +126,13 @@ extension TableViewController {
|
||||||
static let autoplayableAttachmentsView = PassthroughSubject<StatusAttachmentsView?, Never>()
|
static let autoplayableAttachmentsView = PassthroughSubject<StatusAttachmentsView?, Never>()
|
||||||
static let autoplayableAttachmentsViewNotification =
|
static let autoplayableAttachmentsViewNotification =
|
||||||
Notification.Name("com.metabolist.metatext.attachment-view-became-autoplayable")
|
Notification.Name("com.metabolist.metatext.attachment-view-became-autoplayable")
|
||||||
|
|
||||||
|
func report(viewModel: ReportViewModel) {
|
||||||
|
let reportViewController = ReportViewController(viewModel: viewModel)
|
||||||
|
let navigationController = UINavigationController(rootViewController: reportViewController)
|
||||||
|
|
||||||
|
present(navigationController, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableViewController: UITableViewDataSourcePrefetching {
|
extension TableViewController: UITableViewDataSourcePrefetching {
|
||||||
|
@ -325,6 +332,8 @@ private extension TableViewController {
|
||||||
}
|
}
|
||||||
case let .attachment(attachmentViewModel, statusViewModel):
|
case let .attachment(attachmentViewModel, statusViewModel):
|
||||||
present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel)
|
present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel)
|
||||||
|
case let .report(reportViewModel):
|
||||||
|
report(viewModel: reportViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Combine
|
||||||
import DB
|
import DB
|
||||||
import Foundation
|
import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
import MastodonAPI
|
||||||
import MastodonAPIStubs
|
import MastodonAPIStubs
|
||||||
import MockKeychain
|
import MockKeychain
|
||||||
import Secrets
|
import Secrets
|
||||||
|
@ -13,23 +14,24 @@ import ViewModels
|
||||||
|
|
||||||
// swiftlint:disable force_try
|
// swiftlint:disable force_try
|
||||||
|
|
||||||
|
let identityId = Identity.Id()
|
||||||
|
|
||||||
let db: IdentityDatabase = {
|
let db: IdentityDatabase = {
|
||||||
let id = Identity.Id()
|
|
||||||
let db = try! IdentityDatabase(inMemory: true, appGroup: "", keychain: MockKeychain.self)
|
let db = try! IdentityDatabase(inMemory: true, appGroup: "", keychain: MockKeychain.self)
|
||||||
let secrets = Secrets(identityId: id, keychain: MockKeychain.self)
|
let secrets = Secrets(identityId: identityId, keychain: MockKeychain.self)
|
||||||
|
|
||||||
try! secrets.setInstanceURL(.previewInstanceURL)
|
try! secrets.setInstanceURL(.previewInstanceURL)
|
||||||
try! secrets.setAccessToken(UUID().uuidString)
|
try! secrets.setAccessToken(UUID().uuidString)
|
||||||
|
|
||||||
_ = db.createIdentity(id: id, url: .previewInstanceURL, authenticated: true, pending: false)
|
_ = db.createIdentity(id: identityId, url: .previewInstanceURL, authenticated: true, pending: false)
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink { _ in } receiveValue: { _ in }
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
|
||||||
_ = db.updateInstance(.preview, id: id)
|
_ = db.updateInstance(.preview, id: identityId)
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink { _ in } receiveValue: { _ in }
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
|
||||||
_ = db.updateAccount(.preview, id: id)
|
_ = db.updateAccount(.preview, id: identityId)
|
||||||
.receive(on: ImmediateScheduler.shared)
|
.receive(on: ImmediateScheduler.shared)
|
||||||
.sink { _ in } receiveValue: { _ in }
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
|
||||||
|
@ -39,6 +41,22 @@ let db: IdentityDatabase = {
|
||||||
let environment = AppEnvironment.mock(fixtureDatabase: db)
|
let environment = AppEnvironment.mock(fixtureDatabase: db)
|
||||||
let decoder = MastodonDecoder()
|
let decoder = MastodonDecoder()
|
||||||
|
|
||||||
|
extension MastodonAPIClient {
|
||||||
|
static let preview = MastodonAPIClient(
|
||||||
|
session: URLSession(configuration: .stubbing),
|
||||||
|
instanceURL: .previewInstanceURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ContentDatabase {
|
||||||
|
static let preview = try! ContentDatabase(
|
||||||
|
id: identityId,
|
||||||
|
useHomeTimelineLastReadId: false,
|
||||||
|
useNotificationsLastReadId: false,
|
||||||
|
inMemory: true,
|
||||||
|
appGroup: "group.metabolist.metatext",
|
||||||
|
keychain: MockKeychain.self)
|
||||||
|
}
|
||||||
|
|
||||||
public extension URL {
|
public extension URL {
|
||||||
static let previewInstanceURL = URL(string: "https://mastodon.social")!
|
static let previewInstanceURL = URL(string: "https://mastodon.social")!
|
||||||
}
|
}
|
||||||
|
@ -59,4 +77,13 @@ public extension Identification {
|
||||||
static let preview = RootViewModel.preview.navigationViewModel!.identification
|
static let preview = RootViewModel.preview.navigationViewModel!.identification
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension ReportViewModel {
|
||||||
|
static let preview = ReportViewModel(
|
||||||
|
accountService: AccountService(
|
||||||
|
account: .preview,
|
||||||
|
mastodonAPIClient: .preview,
|
||||||
|
contentDatabase: .preview),
|
||||||
|
identification: .preview)
|
||||||
|
}
|
||||||
|
|
||||||
// swiftlint:enable force_try
|
// swiftlint:enable force_try
|
||||||
|
|
|
@ -66,6 +66,10 @@ public extension AccountViewModel {
|
||||||
.eraseToAnyPublisher())
|
.eraseToAnyPublisher())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reportViewModel() -> ReportViewModel {
|
||||||
|
ReportViewModel(accountService: accountService, identification: identification)
|
||||||
|
}
|
||||||
|
|
||||||
func follow() {
|
func follow() {
|
||||||
ignorableOutputEvent(accountService.follow())
|
ignorableOutputEvent(accountService.follow())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,6 @@ public enum CollectionItemEvent {
|
||||||
case ignorableOutput
|
case ignorableOutput
|
||||||
case navigation(Navigation)
|
case navigation(Navigation)
|
||||||
case attachment(AttachmentViewModel, StatusViewModel)
|
case attachment(AttachmentViewModel, StatusViewModel)
|
||||||
|
case report(ReportViewModel)
|
||||||
case share(URL)
|
case share(URL)
|
||||||
}
|
}
|
||||||
|
|
62
ViewModels/Sources/ViewModels/ReportViewModel.swift
Normal file
62
ViewModels/Sources/ViewModels/ReportViewModel.swift
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public final class ReportViewModel: ObservableObject {
|
||||||
|
@Published public var elements: ReportElements
|
||||||
|
public let events: AnyPublisher<Event, Never>
|
||||||
|
public let statusViewModel: StatusViewModel?
|
||||||
|
@Published public private(set) var loading = false
|
||||||
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
|
private let accountService: AccountService
|
||||||
|
private let eventsSubject = PassthroughSubject<Event, Never>()
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
public init(accountService: AccountService, statusService: StatusService? = nil, identification: Identification) {
|
||||||
|
self.accountService = accountService
|
||||||
|
elements = ReportElements(accountId: accountService.account.id)
|
||||||
|
events = eventsSubject.eraseToAnyPublisher()
|
||||||
|
|
||||||
|
if let statusService = statusService {
|
||||||
|
statusViewModel = StatusViewModel(statusService: statusService, identification: identification)
|
||||||
|
elements.statusIds.insert(statusService.status.displayStatus.id)
|
||||||
|
} else {
|
||||||
|
statusViewModel = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ReportViewModel {
|
||||||
|
enum Event {
|
||||||
|
case reported
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountName: String { "@".appending(accountService.account.acct) }
|
||||||
|
|
||||||
|
var accountHost: String {
|
||||||
|
accountService.account.url.host ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLocalAccount: Bool { accountService.isLocal }
|
||||||
|
|
||||||
|
func report() {
|
||||||
|
accountService.report(elements)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true })
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.sink { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.loading = false
|
||||||
|
|
||||||
|
if $0 == .finished {
|
||||||
|
self.eventsSubject.send(.reported)
|
||||||
|
}
|
||||||
|
} receiveValue: { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -215,6 +215,17 @@ public extension StatusViewModel {
|
||||||
eventsSubject.send(Just(.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher())
|
eventsSubject.send(Just(.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reportStatus() {
|
||||||
|
eventsSubject.send(
|
||||||
|
Just(.report(ReportViewModel(
|
||||||
|
accountService: statusService.navigationService.accountService(
|
||||||
|
account: statusService.status.displayStatus.account),
|
||||||
|
statusService: statusService,
|
||||||
|
identification: identification)))
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.eraseToAnyPublisher())
|
||||||
|
}
|
||||||
|
|
||||||
func vote() {
|
func vote() {
|
||||||
eventsSubject.send(
|
eventsSubject.send(
|
||||||
statusService.vote(selectedOptions: pollOptionSelections)
|
statusService.vote(selectedOptions: pollOptionSelections)
|
||||||
|
|
101
Views/ReportView.swift
Normal file
101
Views/ReportView.swift
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct ReportView: View {
|
||||||
|
@StateObject var viewModel: ReportViewModel
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
|
fileprivate var dismissHostingController: (() -> Void)?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
if let statusViewModel = viewModel.statusViewModel {
|
||||||
|
Section {
|
||||||
|
ReportStatusView(viewModel: statusViewModel)
|
||||||
|
.frame(height: Self.statusHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section {
|
||||||
|
Text("report.hint")
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
if viewModel.elements.comment.isEmpty {
|
||||||
|
Text("report.placeholder").foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
TextEditor(text: $viewModel.elements.comment)
|
||||||
|
}
|
||||||
|
if !viewModel.isLocalAccount {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("report.forward.hint")
|
||||||
|
Toggle("report.forward-\(viewModel.accountHost)", isOn: $viewModel.elements.forward)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Group {
|
||||||
|
if viewModel.loading {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
Button("submit") {
|
||||||
|
viewModel.report()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alertItem($viewModel.alertItem)
|
||||||
|
.onReceive(viewModel.events) {
|
||||||
|
switch $0 {
|
||||||
|
case .reported:
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("report.target-\(viewModel.accountName)")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button {
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ReportView {
|
||||||
|
static let statusHeight: CGFloat = 100
|
||||||
|
|
||||||
|
func dismiss() {
|
||||||
|
if let dismissHostingController = dismissHostingController {
|
||||||
|
dismissHostingController()
|
||||||
|
} else {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReportViewController: UIHostingController<ReportView> {
|
||||||
|
init(viewModel: ReportViewModel) {
|
||||||
|
super.init(rootView: ReportView(viewModel: viewModel))
|
||||||
|
|
||||||
|
rootView.dismissHostingController = { [weak self] in self?.dismiss(animated: true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
@objc required dynamic init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
|
struct ReportView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NavigationView {
|
||||||
|
ReportView(viewModel: .preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
27
Views/Status/ReportStatusView.swift
Normal file
27
Views/Status/ReportStatusView.swift
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct ReportStatusView: UIViewRepresentable {
|
||||||
|
private let configuration: StatusContentConfiguration
|
||||||
|
|
||||||
|
init(viewModel: StatusViewModel) {
|
||||||
|
configuration = StatusContentConfiguration(viewModel: viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> StatusView {
|
||||||
|
let view = StatusView(configuration: configuration)
|
||||||
|
|
||||||
|
view.alpha = 0.5
|
||||||
|
view.buttonsStackView.isHidden = true
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: StatusView, context: Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ final class StatusView: UIView {
|
||||||
let favoriteButton = UIButton()
|
let favoriteButton = UIButton()
|
||||||
let shareButton = UIButton()
|
let shareButton = UIButton()
|
||||||
let menuButton = UIButton()
|
let menuButton = UIButton()
|
||||||
|
let buttonsStackView = UIStackView()
|
||||||
|
|
||||||
private let containerStackView = UIStackView()
|
private let containerStackView = UIStackView()
|
||||||
private let sideStackView = UIStackView()
|
private let sideStackView = UIStackView()
|
||||||
|
@ -35,7 +36,6 @@ final class StatusView: UIView {
|
||||||
private let interactionsDividerView = UIView()
|
private let interactionsDividerView = UIView()
|
||||||
private let interactionsStackView = UIStackView()
|
private let interactionsStackView = UIStackView()
|
||||||
private let buttonsDividerView = UIView()
|
private let buttonsDividerView = UIView()
|
||||||
private let buttonsStackView = UIStackView()
|
|
||||||
private let inReplyToView = UIView()
|
private let inReplyToView = UIView()
|
||||||
private let hasReplyFollowingView = UIView()
|
private let hasReplyFollowingView = UIView()
|
||||||
private var statusConfiguration: StatusContentConfiguration
|
private var statusConfiguration: StatusContentConfiguration
|
||||||
|
@ -205,6 +205,16 @@ private extension StatusView {
|
||||||
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() },
|
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() },
|
||||||
for: .touchUpInside)
|
for: .touchUpInside)
|
||||||
|
|
||||||
|
menuButton.showsMenuAsPrimaryAction = true
|
||||||
|
menuButton.menu = UIMenu(children: [
|
||||||
|
UIAction(
|
||||||
|
title: NSLocalizedString("report", comment: ""),
|
||||||
|
image: UIImage(systemName: "flag"),
|
||||||
|
attributes: .destructive) { [weak self] _ in
|
||||||
|
self?.statusConfiguration.viewModel.reportStatus()
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
for button in actionButtons {
|
for button in actionButtons {
|
||||||
button.titleLabel?.font = .preferredFont(forTextStyle: .footnote)
|
button.titleLabel?.font = .preferredFont(forTextStyle: .footnote)
|
||||||
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||||
|
|
Loading…
Reference in a new issue