diff --git a/Packages/Status/Sources/Status/List/StatusesListView.swift b/Packages/Status/Sources/Status/List/StatusesListView.swift index efae9440..9eebdb4a 100644 --- a/Packages/Status/Sources/Status/List/StatusesListView.swift +++ b/Packages/Status/Sources/Status/List/StatusesListView.swift @@ -9,6 +9,7 @@ public struct StatusesListView: View where Fetcher: StatusesFetcher { @EnvironmentObject private var theme: Theme @ObservedObject private var fetcher: Fetcher + // Whether this status is on a remote local timeline (many actions are unavailable if so) private let isRemote: Bool private let routerPath: RouterPath private let client: Client diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 73d20413..1bcd3f31 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -99,6 +99,11 @@ public struct StatusRowView: View { } .contextMenu { contextMenu + .onAppear { + Task { + await viewModel.loadAuthorRelationship() + } + } } .swipeActions(edge: .trailing) { // The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them. diff --git a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift index 1d9dc8fa..72c24bff 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift @@ -10,6 +10,7 @@ import SwiftUI public class StatusRowViewModel: ObservableObject { let status: Status let isFocused: Bool + // Whether this status is on a remote local timeline (many actions are unavailable if so) let isRemote: Bool let showActions: Bool let textDisabled: Bool @@ -32,6 +33,17 @@ public class StatusRowViewModel: ObservableObject { @Published var isLoadingRemoteContent: Bool = false @Published var localStatusId: String? @Published var localStatus: Status? + + // The relationship our user has to the author of this post, if available + @Published var authorRelationship: Relationship? { + didSet { + // if we are newly blocking or muting the author, force collapse post so it goes away + if let relationship = authorRelationship, + relationship.blocking || relationship.muting { + lineLimit = 0 + } + } + } // used by the button to expand a collapsed post @Published var isCollapsed: Bool = true { @@ -182,6 +194,11 @@ public class StatusRowViewModel: ObservableObject { } } + func loadAuthorRelationship() async { + let relationships: [Relationship]? = try? await client.get(endpoint: Accounts.relationships(ids: [status.reblog?.account.id ?? status.account.id])) + authorRelationship = relationships?.first + } + private func embededStatusURL() -> URL? { let content = finalStatus.content if !content.statusesURLs.isEmpty, diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift index 7bf1dbe4..04f6f7f8 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift @@ -7,6 +7,7 @@ import SwiftUI struct StatusRowContextMenu: View { @Environment(\.displayScale) var displayScale + @EnvironmentObject private var client: Client @EnvironmentObject private var sceneDelegate: SceneDelegate @EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var account: CurrentAccount @@ -186,6 +187,68 @@ struct StatusRowContextMenu: View { } label: { Label("status.action.message", systemImage: "tray.full") } + + + if viewModel.authorRelationship?.blocking == true { + Button { + Task { + do { + let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account + viewModel.authorRelationship = try await client.post(endpoint: Accounts.unblock(id: operationAccount.id)) + } catch { + print("Error while unblocking: \(error.localizedDescription)") + } + } + } label: { + Label("account.action.unblock", systemImage: "person.crop.circle.badge.exclamationmark") + } + } else { + Button { + Task { + do { + let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account + viewModel.authorRelationship = try await client.post(endpoint: Accounts.block(id: operationAccount.id)) + } catch { + print("Error while blocking: \(error.localizedDescription)") + } + } + } label: { + Label("account.action.block", systemImage: "person.crop.circle.badge.xmark") + } + } + + if viewModel.authorRelationship?.muting == true { + Button { + Task { + do { + let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account + viewModel.authorRelationship = try await client.post(endpoint: Accounts.unmute(id: operationAccount.id)) + } catch { + print("Error while unmuting: \(error.localizedDescription)") + } + } + } label: { + Label("account.action.unmute", systemImage: "speaker") + } + } else { + Menu { + ForEach(Duration.mutingDurations(), id: \.rawValue) { duration in + Button(duration.description) { + Task { + do { + let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account + viewModel.authorRelationship = try await client.post(endpoint: Accounts.mute(id: operationAccount.id, json: MuteData(duration: duration.rawValue))) + } catch { + print("Error while muting: \(error.localizedDescription)") + } + } + } + } + } label: { + Label("account.action.mute", systemImage: "speaker.slash") + } + } + } } Section { diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift index e365c83c..bf84c364 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift @@ -113,9 +113,14 @@ struct StatusRowHeaderView: View { private var contextMenuButton: some View { Menu { StatusRowContextMenu(viewModel: viewModel) + .onAppear { + Task { + await viewModel.loadAuthorRelationship() + } + } } label: { Image(systemName: "ellipsis") - .frame(width: 20, height: 20) + .frame(width: 40, height: 40) } .menuStyle(.borderlessButton) .foregroundColor(.gray)