forked from shadowfacts/Tusker
Extract filter create/update/delete logic into separate services
This commit is contained in:
parent
83ca7f1321
commit
f71804f094
|
@ -61,6 +61,9 @@
|
|||
D61F75AB293AF11400C0B37F /* FilterKeywordMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75AA293AF11400C0B37F /* FilterKeywordMO.swift */; };
|
||||
D61F75AD293AF39000C0B37F /* Filter+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75AC293AF39000C0B37F /* Filter+Helpers.swift */; };
|
||||
D61F75AF293AF50C00C0B37F /* EditedFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75AE293AF50C00C0B37F /* EditedFilter.swift */; };
|
||||
D61F75B1293BD85300C0B37F /* CreateFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75B0293BD85300C0B37F /* CreateFilterService.swift */; };
|
||||
D61F75B3293BD89C00C0B37F /* UpdateFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75B2293BD89C00C0B37F /* UpdateFilterService.swift */; };
|
||||
D61F75B5293BD97400C0B37F /* DeleteFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75B4293BD97400C0B37F /* DeleteFilterService.swift */; };
|
||||
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
||||
|
@ -438,6 +441,9 @@
|
|||
D61F75AA293AF11400C0B37F /* FilterKeywordMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterKeywordMO.swift; sourceTree = "<group>"; };
|
||||
D61F75AC293AF39000C0B37F /* Filter+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Filter+Helpers.swift"; sourceTree = "<group>"; };
|
||||
D61F75AE293AF50C00C0B37F /* EditedFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditedFilter.swift; sourceTree = "<group>"; };
|
||||
D61F75B0293BD85300C0B37F /* CreateFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateFilterService.swift; sourceTree = "<group>"; };
|
||||
D61F75B2293BD89C00C0B37F /* UpdateFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateFilterService.swift; sourceTree = "<group>"; };
|
||||
D61F75B4293BD97400C0B37F /* DeleteFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFilterService.swift; sourceTree = "<group>"; };
|
||||
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
|
||||
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1567,6 +1573,9 @@
|
|||
D6F6A551291F098700F496A8 /* RenameListService.swift */,
|
||||
D6F6A553291F0D9600F496A8 /* DeleteListService.swift */,
|
||||
D61F75952937037800C0B37F /* ToggleFollowHashtagService.swift */,
|
||||
D61F75B0293BD85300C0B37F /* CreateFilterService.swift */,
|
||||
D61F75B2293BD89C00C0B37F /* UpdateFilterService.swift */,
|
||||
D61F75B4293BD97400C0B37F /* DeleteFilterService.swift */,
|
||||
);
|
||||
path = API;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1949,6 +1958,7 @@
|
|||
D6DF95C12533F5DE0027A9B6 /* RelationshipMO.swift in Sources */,
|
||||
D6ADB6EE28EA74E8009924AB /* UIView+Configure.swift in Sources */,
|
||||
D623A5412635FB3C0095BD04 /* PollOptionView.swift in Sources */,
|
||||
D61F75B1293BD85300C0B37F /* CreateFilterService.swift in Sources */,
|
||||
D65C6BF525478A9C00A6E89C /* BackgroundableViewController.swift in Sources */,
|
||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
|
||||
D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */,
|
||||
|
@ -2055,6 +2065,7 @@
|
|||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
|
||||
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */,
|
||||
D61F75B5293BD97400C0B37F /* DeleteFilterService.swift in Sources */,
|
||||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||
D6DFC6A0242C4CCC00ACC392 /* Weak.swift in Sources */,
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||
|
@ -2101,6 +2112,7 @@
|
|||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
||||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */,
|
||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||
D61F75B3293BD89C00C0B37F /* UpdateFilterService.swift in Sources */,
|
||||
D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */,
|
||||
D6D12B56292D57E800D528E1 /* AccountCollectionViewCell.swift in Sources */,
|
||||
D6A6C10525B6138A00298D0F /* StatusTablePrefetching.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// CreateFilterService.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/3/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
@MainActor
|
||||
class CreateFilterService {
|
||||
private let filter: EditedFilter
|
||||
private let mastodonController: MastodonController
|
||||
|
||||
init(filter: EditedFilter, mastodonController: MastodonController) {
|
||||
self.filter = filter
|
||||
self.mastodonController = mastodonController
|
||||
}
|
||||
|
||||
func run() 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// DeleteFilterService.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/3/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
@MainActor
|
||||
class DeleteFilterService {
|
||||
private let filter: FilterMO
|
||||
private let mastodonController: MastodonController
|
||||
|
||||
init(filter: FilterMO, mastodonController: MastodonController) {
|
||||
self.filter = filter
|
||||
self.mastodonController = mastodonController
|
||||
}
|
||||
|
||||
func run() async throws {
|
||||
let req = FilterV1.delete(filter.id)
|
||||
_ = try await mastodonController.run(req)
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
context.delete(filter)
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// UpdateFilterService.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/3/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
@MainActor
|
||||
class UpdateFilterService {
|
||||
private let filter: EditedFilter
|
||||
private let mastodonController: MastodonController
|
||||
|
||||
init(filter: EditedFilter, mastodonController: MastodonController) {
|
||||
self.filter = filter
|
||||
self.mastodonController = mastodonController
|
||||
}
|
||||
|
||||
func run() async throws {
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
let mo = try context.fetch(FilterMO.fetchRequest(id: filter.id!)).first!
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
mo.updateFrom(apiFilter: updateFrom, context: context)
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,12 @@ public final class FilterMO: NSManagedObject {
|
|||
return NSFetchRequest(entityName: "Filter")
|
||||
}
|
||||
|
||||
@nonobjc public class func fetchRequest(id: String) -> NSFetchRequest<FilterMO> {
|
||||
let req = NSFetchRequest<FilterMO>(entityName: "Filter")
|
||||
req.predicate = NSPredicate(format: "id = %@", id)
|
||||
return req
|
||||
}
|
||||
|
||||
@NSManaged public var id: String
|
||||
@NSManaged public var title: String?
|
||||
@NSManaged private var context: String
|
||||
|
|
|
@ -29,7 +29,6 @@ struct EditFilterView: View {
|
|||
|
||||
@ObservedObject var filter: EditedFilter
|
||||
let create: Bool
|
||||
let saveFilter: (EditedFilter) async throws -> Void
|
||||
@EnvironmentObject private var mastodonController: MastodonController
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var originalFilter: EditedFilter
|
||||
|
@ -37,10 +36,9 @@ struct EditFilterView: View {
|
|||
@State private var isSaving = false
|
||||
@State private var saveError: (any Error)?
|
||||
|
||||
init(filter: EditedFilter, create: Bool, saveFilter: @escaping (EditedFilter) async throws -> Void) {
|
||||
init(filter: EditedFilter, create: Bool) {
|
||||
self.filter = filter
|
||||
self.create = create
|
||||
self.saveFilter = saveFilter
|
||||
self._originalFilter = State(wrappedValue: EditedFilter(copying: filter))
|
||||
if let expiresIn = filter.expiresIn {
|
||||
self._expiresIn = State(wrappedValue: Self.expiresInOptions.min(by: { a, b in
|
||||
|
@ -158,16 +156,7 @@ struct EditFilterView: View {
|
|||
.progressViewStyle(.circular)
|
||||
} else {
|
||||
Button(create ? "Create" : "Save") {
|
||||
Task {
|
||||
do {
|
||||
isSaving = true
|
||||
try await saveFilter(filter)
|
||||
dismiss()
|
||||
} catch {
|
||||
isSaving = false
|
||||
saveError = error
|
||||
}
|
||||
}
|
||||
saveFilter()
|
||||
}
|
||||
.disabled(!filter.isValid(for: mastodonController) || !edited)
|
||||
}
|
||||
|
@ -182,6 +171,23 @@ struct EditFilterView: View {
|
|||
edited = true
|
||||
})
|
||||
}
|
||||
|
||||
private func saveFilter() {
|
||||
Task {
|
||||
do {
|
||||
isSaving = true
|
||||
if create {
|
||||
try await CreateFilterService(filter: filter, mastodonController: mastodonController).run()
|
||||
} else {
|
||||
try await UpdateFilterService(filter: filter, mastodonController: mastodonController).run()
|
||||
}
|
||||
dismiss()
|
||||
} catch {
|
||||
isSaving = false
|
||||
saveError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct FilterContextToggleStyle: ToggleStyle {
|
||||
|
|
|
@ -25,7 +25,6 @@ struct FiltersList: View {
|
|||
@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, *) {
|
||||
|
@ -52,15 +51,15 @@ struct FiltersList: View {
|
|||
List {
|
||||
Section {
|
||||
NavigationLink {
|
||||
EditFilterView(filter: EditedFilter(), create: true, saveFilter: createFilter)
|
||||
EditFilterView(filter: EditedFilter(), create: true)
|
||||
} label: {
|
||||
Label("Add Filter", systemImage: "plus")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
filtersSection(unexpiredFilters)
|
||||
filtersSection(expiredFilters)
|
||||
filtersSection(unexpiredFilters, header: Text("Active"))
|
||||
filtersSection(expiredFilters, header: Text("Expired"))
|
||||
}
|
||||
.navigationTitle(Text("Filters"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
@ -78,23 +77,16 @@ struct FiltersList: View {
|
|||
}, 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 {
|
||||
private func filtersSection(_ filters: [FilterMO], header: some View) -> some View {
|
||||
Section {
|
||||
ForEach(filters, id: \.id) { filter in
|
||||
NavigationLink {
|
||||
EditFilterView(filter: EditedFilter(filter), create: false, saveFilter: updateFilter)
|
||||
EditFilterView(filter: EditedFilter(filter), create: false)
|
||||
} label: {
|
||||
FilterRow(filter: filter)
|
||||
}
|
||||
|
@ -112,73 +104,20 @@ struct FiltersList: View {
|
|||
deleteFilter(filter)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
header
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteFilter(_ filter: FilterMO) {
|
||||
Task { @MainActor in
|
||||
let req = FilterV1.delete(filter.id)
|
||||
Task {
|
||||
do {
|
||||
_ = try await mastodonController.run(req)
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
context.delete(filter)
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
try await DeleteFilterService(filter: filter, mastodonController: mastodonController).run()
|
||||
} 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 {
|
||||
|
|
Loading…
Reference in New Issue