Support filters in statuses

This commit is contained in:
Thomas Ricouard 2023-01-03 12:24:15 +01:00
parent 37a5567fe7
commit f4f8b81f6c
5 changed files with 89 additions and 32 deletions

View file

@ -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
}

View file

@ -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]
}

View file

@ -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

View file

@ -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 {

View file

@ -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