mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-26 18:21:00 +00:00
Add feature to block or mute user directly from post (#1460)
* Make status context menu button frame tap target larger This makes it much easier to hit on the first try, and doesn't appear to negatively impact the layout. * Add feature to block or mute user directly from post To avoid calling the /accounts/relationships endpoint for every single status displayed, the data is only loaded when the menu is activated. When the API call comes back, the items are added to the menu (updating the view model appears to cause the menu to update, even while it is displayed) Borrowed blocking & muting logic/menu items from AccountDetailContextMenu.
This commit is contained in:
parent
8c97c9e1be
commit
194e3aea74
5 changed files with 92 additions and 1 deletions
|
@ -9,6 +9,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
@ObservedObject private var fetcher: Fetcher
|
@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 isRemote: Bool
|
||||||
private let routerPath: RouterPath
|
private let routerPath: RouterPath
|
||||||
private let client: Client
|
private let client: Client
|
||||||
|
|
|
@ -99,6 +99,11 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
contextMenu
|
contextMenu
|
||||||
|
.onAppear {
|
||||||
|
Task {
|
||||||
|
await viewModel.loadAuthorRelationship()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.swipeActions(edge: .trailing) {
|
.swipeActions(edge: .trailing) {
|
||||||
// The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them.
|
// The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
||||||
public class StatusRowViewModel: ObservableObject {
|
public class StatusRowViewModel: ObservableObject {
|
||||||
let status: Status
|
let status: Status
|
||||||
let isFocused: Bool
|
let isFocused: Bool
|
||||||
|
// Whether this status is on a remote local timeline (many actions are unavailable if so)
|
||||||
let isRemote: Bool
|
let isRemote: Bool
|
||||||
let showActions: Bool
|
let showActions: Bool
|
||||||
let textDisabled: Bool
|
let textDisabled: Bool
|
||||||
|
@ -33,6 +34,17 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
@Published var localStatusId: String?
|
@Published var localStatusId: String?
|
||||||
@Published var localStatus: Status?
|
@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
|
// used by the button to expand a collapsed post
|
||||||
@Published var isCollapsed: Bool = true {
|
@Published var isCollapsed: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -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? {
|
private func embededStatusURL() -> URL? {
|
||||||
let content = finalStatus.content
|
let content = finalStatus.content
|
||||||
if !content.statusesURLs.isEmpty,
|
if !content.statusesURLs.isEmpty,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import SwiftUI
|
||||||
struct StatusRowContextMenu: View {
|
struct StatusRowContextMenu: View {
|
||||||
@Environment(\.displayScale) var displayScale
|
@Environment(\.displayScale) var displayScale
|
||||||
|
|
||||||
|
@EnvironmentObject private var client: Client
|
||||||
@EnvironmentObject private var sceneDelegate: SceneDelegate
|
@EnvironmentObject private var sceneDelegate: SceneDelegate
|
||||||
@EnvironmentObject private var preferences: UserPreferences
|
@EnvironmentObject private var preferences: UserPreferences
|
||||||
@EnvironmentObject private var account: CurrentAccount
|
@EnvironmentObject private var account: CurrentAccount
|
||||||
|
@ -186,6 +187,68 @@ struct StatusRowContextMenu: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.action.message", systemImage: "tray.full")
|
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 {
|
Section {
|
||||||
|
|
|
@ -113,9 +113,14 @@ struct StatusRowHeaderView: View {
|
||||||
private var contextMenuButton: some View {
|
private var contextMenuButton: some View {
|
||||||
Menu {
|
Menu {
|
||||||
StatusRowContextMenu(viewModel: viewModel)
|
StatusRowContextMenu(viewModel: viewModel)
|
||||||
|
.onAppear {
|
||||||
|
Task {
|
||||||
|
await viewModel.loadAuthorRelationship()
|
||||||
|
}
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "ellipsis")
|
Image(systemName: "ellipsis")
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 40, height: 40)
|
||||||
}
|
}
|
||||||
.menuStyle(.borderlessButton)
|
.menuStyle(.borderlessButton)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
Loading…
Reference in a new issue