Tusker/Tusker/Screens/Filters/FiltersView.swift

189 lines
7.0 KiB
Swift

//
// FiltersView.swift
// Tusker
//
// Created by Shadowfacts on 11/30/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
struct FiltersView: View {
let mastodonController: MastodonController
var body: some View {
FiltersList()
.environmentObject(mastodonController)
.environment(\.managedObjectContext, mastodonController.persistentContainer.viewContext)
}
}
struct FiltersList: View {
@EnvironmentObject private var mastodonController: MastodonController
@FetchRequest(sortDescriptors: []) private var filters: FetchedResults<FilterMO>
@Environment(\.dismiss) private var dismiss
@State private var deletionError: (any Error)?
@State private var updatingError: (any Error)?
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
navigationBody
}
} else {
NavigationView {
navigationBody
}
.navigationViewStyle(.stack)
}
}
private var unexpiredFilters: [FilterMO] {
filters.filter { $0.expiresAt == nil || $0.expiresAt! > Date() }.sorted(using: SemiCaseSensitiveComparator.keyPath(\.titleOrKeyword))
}
private var expiredFilters: [FilterMO] {
filters.filter { $0.expiresAt != nil && $0.expiresAt! <= Date() }.sorted(using: SemiCaseSensitiveComparator.keyPath(\.titleOrKeyword))
}
private var navigationBody: some View {
List {
Section {
NavigationLink {
EditFilterView(filter: EditedFilter(), create: true, saveFilter: createFilter)
} label: {
Label("Add Filter", systemImage: "plus")
.foregroundColor(.accentColor)
}
}
filtersSection(unexpiredFilters)
filtersSection(expiredFilters)
}
.navigationTitle(Text("Filters"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
dismiss()
}
}
}
.alertWithData("Error Deleting Filter", data: $deletionError, actions: { _ in
Button("OK") {
self.deletionError = nil
}
}, message: { error in
Text(error.localizedDescription)
})
.alertWithData("Error Update Filter", data: $updatingError, actions: { _ in
Button("OK") {
self.updatingError = nil
}
}, message: { error in
Text(error.localizedDescription)
})
.task {
await mastodonController.loadFilters()
}
}
private func filtersSection(_ filters: [FilterMO]) -> some View {
Section {
ForEach(filters, id: \.id) { filter in
NavigationLink {
EditFilterView(filter: EditedFilter(filter), create: false, saveFilter: updateFilter)
} label: {
FilterRow(filter: filter)
}
.contextMenu {
Button(role: .destructive) {
deleteFilter(filter)
} label: {
Text("Delete Filter")
}
}
}
.onDelete { indices in
for filter in indices.map({ filters[$0] }) {
deleteFilter(filter)
}
}
}
}
private func deleteFilter(_ filter: FilterMO) {
Task { @MainActor in
let req = FilterV1.delete(filter.id)
do {
_ = try await mastodonController.run(req)
let context = mastodonController.persistentContainer.viewContext
context.delete(filter)
mastodonController.persistentContainer.save(context: context)
} catch {
self.deletionError = error
}
}
}
private func updateFilter(_ filter: EditedFilter) async throws {
let mo = filters.first(where: { $0.id == filter.id })!
let updateFrom: AnyFilter
if mastodonController.instanceFeatures.filtersV2 {
var updates = filter.keywords.map {
if let id = $0.id {
return FilterV2.KeywordUpdate.update(id: id, keyword: $0.keyword, wholeWord: $0.wholeWord)
} else {
return FilterV2.KeywordUpdate.add(keyword: $0.keyword, wholeWord: $0.wholeWord)
}
}
for existing in mo.keywordMOs where !filter.keywords.contains(where: { existing.id == $0.id }) {
if let id = existing.id {
updates.append(.destroy(id: id))
}
}
let req = FilterV2.update(filter.id!, title: filter.title ?? "", context: filter.contexts, expiresIn: filter.expiresIn, action: filter.action, keywords: updates)
let (updated, _) = try await mastodonController.run(req)
updateFrom = .v2(updated)
} else {
let req = FilterV1.update(filter.id!, phrase: filter.keywords.first!.keyword, context: filter.contexts, irreversible: false, wholeWord: filter.keywords.first!.wholeWord, expiresIn: filter.expiresIn)
let (updated, _) = try await mastodonController.run(req)
updateFrom = .v1(updated)
}
let context = mastodonController.persistentContainer.viewContext
mo.updateFrom(apiFilter: updateFrom, context: context)
mastodonController.persistentContainer.save(context: context)
}
private func createFilter(_ filter: EditedFilter) async throws {
let updateFrom: AnyFilter
if mastodonController.instanceFeatures.filtersV2 {
let updates = filter.keywords.map {
FilterV2.KeywordUpdate.add(keyword: $0.keyword, wholeWord: $0.wholeWord)
}
let req = FilterV2.create(title: filter.title!, context: filter.contexts, expiresIn: filter.expiresIn, action: filter.action, keywords: updates)
let (updated, _) = try await mastodonController.run(req)
updateFrom = .v2(updated)
} else {
let req = Client.createFilterV1(phrase: filter.keywords.first!.keyword, context: filter.contexts, irreversible: nil, wholeWord: filter.keywords.first!.wholeWord, expiresIn: filter.expiresIn)
let (updated, _) = try await mastodonController.run(req)
updateFrom = .v1(updated)
}
let context = mastodonController.persistentContainer.viewContext
let mo = FilterMO(context: context)
mo.updateFrom(apiFilter: updateFrom, context: context)
mastodonController.persistentContainer.save(context: context)
}
}
//struct FiltersView_Previews: PreviewProvider {
// static var previews: some View {
// FiltersView()
// }
//}