From f4f8b81f6c6ab35d2bf573121357aaacef796a76 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 3 Jan 2023 12:24:15 +0100 Subject: [PATCH] Support filters in statuses --- Packages/Models/Sources/Models/Filter.swift | 22 +++++ Packages/Models/Sources/Models/Status.swift | 6 +- .../Sources/Status/Row/StatusRowView.swift | 82 ++++++++++++------- .../Status/Row/StatusRowViewModel.swift | 7 ++ README.md | 4 +- 5 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 Packages/Models/Sources/Models/Filter.swift diff --git a/Packages/Models/Sources/Models/Filter.swift b/Packages/Models/Sources/Models/Filter.swift new file mode 100644 index 00000000..c8c8abf0 --- /dev/null +++ b/Packages/Models/Sources/Models/Filter.swift @@ -0,0 +1,22 @@ +import Foundation + +public struct Filtered: Codable { + public let filter: Filter + public let keywordMatches: [String]? +} + +public struct Filter: Codable, Identifiable { + public enum Action: String, Codable { + case warn, hide + } + + public enum Context: String, Codable { + case home, notifications, account, thread + case pub = "public" + } + + public let id: String + public let title: String + public let context: [String] + public let filterAction: Action +} diff --git a/Packages/Models/Sources/Models/Status.swift b/Packages/Models/Sources/Models/Status.swift index a2042faa..0b87f579 100644 --- a/Packages/Models/Sources/Models/Status.swift +++ b/Packages/Models/Sources/Models/Status.swift @@ -38,6 +38,7 @@ public protocol AnyStatus { var visibility: Visibility { get } var poll: Poll? { get } var spoilerText: String { get } + var filtered: [Filtered] { get } } @@ -68,6 +69,7 @@ public struct Status: AnyStatus, Codable, Identifiable { public let visibility: Visibility public let poll: Poll? public let spoilerText: String + public let filtered: [Filtered] public static func placeholder() -> Status { .init(id: UUID().uuidString, @@ -91,7 +93,8 @@ public struct Status: AnyStatus, Codable, Identifiable { inReplyToAccountId: nil, visibility: .pub, poll: nil, - spoilerText: "") + spoilerText: "", + filtered: []) } public static func placeholders() -> [Status] { @@ -125,4 +128,5 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable { public let visibility: Visibility public let poll: Poll? public let spoilerText: String + public let filtered: [Filtered] } diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 1585f739..c51a8e87 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -18,44 +18,66 @@ public struct StatusRowView: View { } public var body: some View { - HStack(alignment: .top, spacing: .statusColumnsSpacing) { - if !viewModel.isCompact, - theme.avatarPosition == .leading, - let status: AnyStatus = viewModel.status.reblog ?? viewModel.status { - Button { - routeurPath.navigate(to: .accountDetailWithAccount(account: status.account)) - } label: { - AvatarView(url: status.account.avatar, size: .status) + if viewModel.isFiltered, let filter = viewModel.filter { + switch filter.filter.filterAction { + case .warn: + makeFilterView(filter: filter.filter) + case .hide: + EmptyView() + } + } else { + HStack(alignment: .top, spacing: .statusColumnsSpacing) { + if !viewModel.isCompact, + theme.avatarPosition == .leading, + let status: AnyStatus = viewModel.status.reblog ?? viewModel.status { + Button { + routeurPath.navigate(to: .accountDetailWithAccount(account: status.account)) + } label: { + AvatarView(url: status.account.avatar, size: .status) + } + } + VStack(alignment: .leading) { + if !viewModel.isCompact { + reblogView + replyView + } + statusView + if !viewModel.isCompact { + StatusActionsView(viewModel: viewModel) + .padding(.vertical, 8) + .tint(viewModel.isFocused ? theme.tintColor : .gray) + .contentShape(Rectangle()) + .onTapGesture { + routeurPath.navigate(to: .statusDetail(id: viewModel.status.reblog?.id ?? viewModel.status.id)) + } + } } } - VStack(alignment: .leading) { - if !viewModel.isCompact { - reblogView - replyView - } - statusView - if !viewModel.isCompact { - StatusActionsView(viewModel: viewModel) - .padding(.vertical, 8) - .tint(viewModel.isFocused ? theme.tintColor : .gray) - .contentShape(Rectangle()) - .onTapGesture { - routeurPath.navigate(to: .statusDetail(id: viewModel.status.reblog?.id ?? viewModel.status.id)) - } + .onAppear { + viewModel.client = client + if !viewModel.isCompact, viewModel.embededStatus == nil { + Task { + await viewModel.loadEmbededStatus() + } } } + .contextMenu { + contextMenu + } } - .onAppear { - viewModel.client = client - if !viewModel.isCompact, viewModel.embededStatus == nil { - Task { - await viewModel.loadEmbededStatus() + } + + private func makeFilterView(filter: Filter) -> some View { + HStack { + Text("Filtered by: \(filter.title)") + Button { + withAnimation { + viewModel.isFiltered = false } + } label: { + Text("Show anyway") } } - .contextMenu { - contextMenu - } } @ViewBuilder diff --git a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift index 25fc7eaa..81a23d2b 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift @@ -16,6 +16,11 @@ public class StatusRowViewModel: ObservableObject { @Published var embededStatus: Status? @Published var displaySpoiler: Bool = false @Published var isEmbedLoading: Bool = true + @Published var isFiltered: Bool = false + + var filter: Filtered? { + status.reblog?.filtered.first ?? status.filtered.first + } var client: Client? @@ -36,6 +41,8 @@ public class StatusRowViewModel: ObservableObject { self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount self.displaySpoiler = !status.spoilerText.isEmpty + + self.isFiltered = filter != nil } func loadEmbededStatus() async { diff --git a/README.md b/README.md index bc3e9291..45f8cc76 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ For contributors and myself, here is a todo list of features that could be added - [ ] Handle emoji in status - [X] Light theme - [X] More themes -- [ ] Honor & display server side features (filter, default visibility, etc...) +- [ ] Display & Edit server side features (filter, default visibility, etc...) +- [X] Honor filters for statuses. +- [ ] Edit filters. - [X] Open remote status locally - [ ] More context menu everywhere - [ ] Support pinned posts