mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-10-31 22:28:58 +00:00
Support filters in statuses
This commit is contained in:
parent
37a5567fe7
commit
f4f8b81f6c
5 changed files with 89 additions and 32 deletions
22
Packages/Models/Sources/Models/Filter.swift
Normal file
22
Packages/Models/Sources/Models/Filter.swift
Normal 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
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ public protocol AnyStatus {
|
||||||
var visibility: Visibility { get }
|
var visibility: Visibility { get }
|
||||||
var poll: Poll? { get }
|
var poll: Poll? { get }
|
||||||
var spoilerText: String { 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 visibility: Visibility
|
||||||
public let poll: Poll?
|
public let poll: Poll?
|
||||||
public let spoilerText: String
|
public let spoilerText: String
|
||||||
|
public let filtered: [Filtered]
|
||||||
|
|
||||||
public static func placeholder() -> Status {
|
public static func placeholder() -> Status {
|
||||||
.init(id: UUID().uuidString,
|
.init(id: UUID().uuidString,
|
||||||
|
@ -91,7 +93,8 @@ public struct Status: AnyStatus, Codable, Identifiable {
|
||||||
inReplyToAccountId: nil,
|
inReplyToAccountId: nil,
|
||||||
visibility: .pub,
|
visibility: .pub,
|
||||||
poll: nil,
|
poll: nil,
|
||||||
spoilerText: "")
|
spoilerText: "",
|
||||||
|
filtered: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func placeholders() -> [Status] {
|
public static func placeholders() -> [Status] {
|
||||||
|
@ -125,4 +128,5 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable {
|
||||||
public let visibility: Visibility
|
public let visibility: Visibility
|
||||||
public let poll: Poll?
|
public let poll: Poll?
|
||||||
public let spoilerText: String
|
public let spoilerText: String
|
||||||
|
public let filtered: [Filtered]
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,44 +18,66 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
HStack(alignment: .top, spacing: .statusColumnsSpacing) {
|
if viewModel.isFiltered, let filter = viewModel.filter {
|
||||||
if !viewModel.isCompact,
|
switch filter.filter.filterAction {
|
||||||
theme.avatarPosition == .leading,
|
case .warn:
|
||||||
let status: AnyStatus = viewModel.status.reblog ?? viewModel.status {
|
makeFilterView(filter: filter.filter)
|
||||||
Button {
|
case .hide:
|
||||||
routeurPath.navigate(to: .accountDetailWithAccount(account: status.account))
|
EmptyView()
|
||||||
} label: {
|
}
|
||||||
AvatarView(url: status.account.avatar, size: .status)
|
} 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) {
|
.onAppear {
|
||||||
if !viewModel.isCompact {
|
viewModel.client = client
|
||||||
reblogView
|
if !viewModel.isCompact, viewModel.embededStatus == nil {
|
||||||
replyView
|
Task {
|
||||||
}
|
await viewModel.loadEmbededStatus()
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.contextMenu {
|
||||||
|
contextMenu
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
}
|
||||||
viewModel.client = client
|
|
||||||
if !viewModel.isCompact, viewModel.embededStatus == nil {
|
private func makeFilterView(filter: Filter) -> some View {
|
||||||
Task {
|
HStack {
|
||||||
await viewModel.loadEmbededStatus()
|
Text("Filtered by: \(filter.title)")
|
||||||
|
Button {
|
||||||
|
withAnimation {
|
||||||
|
viewModel.isFiltered = false
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Show anyway")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contextMenu {
|
|
||||||
contextMenu
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
@Published var embededStatus: Status?
|
@Published var embededStatus: Status?
|
||||||
@Published var displaySpoiler: Bool = false
|
@Published var displaySpoiler: Bool = false
|
||||||
@Published var isEmbedLoading: Bool = true
|
@Published var isEmbedLoading: Bool = true
|
||||||
|
@Published var isFiltered: Bool = false
|
||||||
|
|
||||||
|
var filter: Filtered? {
|
||||||
|
status.reblog?.filtered.first ?? status.filtered.first
|
||||||
|
}
|
||||||
|
|
||||||
var client: Client?
|
var client: Client?
|
||||||
|
|
||||||
|
@ -36,6 +41,8 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
self.reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
|
||||||
self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount
|
self.repliesCount = status.reblog?.repliesCount ?? status.repliesCount
|
||||||
self.displaySpoiler = !status.spoilerText.isEmpty
|
self.displaySpoiler = !status.spoilerText.isEmpty
|
||||||
|
|
||||||
|
self.isFiltered = filter != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEmbededStatus() async {
|
func loadEmbededStatus() async {
|
||||||
|
|
|
@ -31,7 +31,9 @@ For contributors and myself, here is a todo list of features that could be added
|
||||||
- [ ] Handle emoji in status
|
- [ ] Handle emoji in status
|
||||||
- [X] Light theme
|
- [X] Light theme
|
||||||
- [X] More themes
|
- [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
|
- [X] Open remote status locally
|
||||||
- [ ] More context menu everywhere
|
- [ ] More context menu everywhere
|
||||||
- [ ] Support pinned posts
|
- [ ] Support pinned posts
|
||||||
|
|
Loading…
Reference in a new issue