Filter management

This commit is contained in:
Justin Mazzocchi 2020-08-29 17:32:34 -07:00
parent b073f31e77
commit cd59aeab0e
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
8 changed files with 72 additions and 31 deletions

View file

@ -142,6 +142,20 @@ extension ContentDatabase {
.publisher(in: databaseQueue) .publisher(in: databaseQueue)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func activeFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> {
ValueObservation.tracking(Filter.filter(Column("expiresAt") == nil || Column("expiresAt") > date).fetchAll)
.removeDuplicates()
.publisher(in: databaseQueue)
.eraseToAnyPublisher()
}
func expiredFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> {
ValueObservation.tracking(Filter.filter(Column("expiresAt") < date).fetchAll)
.removeDuplicates()
.publisher(in: databaseQueue)
.eraseToAnyPublisher()
}
} }
private extension ContentDatabase { private extension ContentDatabase {

View file

@ -29,6 +29,8 @@
"preferences.notification-types.reblog" = "Reblog"; "preferences.notification-types.reblog" = "Reblog";
"preferences.notification-types.mention" = "Mention"; "preferences.notification-types.mention" = "Mention";
"preferences.notification-types.poll" = "Poll"; "preferences.notification-types.poll" = "Poll";
"filters.active" = "Active";
"filters.expired" = "Expired";
"filter.add-new" = "Add New Filter"; "filter.add-new" = "Add New Filter";
"filter.edit" = "Edit Filter"; "filter.edit" = "Edit Filter";
"filter.keyword-or-phrase" = "Keyword or phrase"; "filter.keyword-or-phrase" = "Keyword or phrase";

View file

@ -78,17 +78,9 @@ private extension FilterEndpoint {
"whole_word": wholeWord] "whole_word": wholeWord]
if let expiresIn = expiresIn { if let expiresIn = expiresIn {
params["expires_in"] = Self.dateFormatter.string(from: expiresIn) params["expires_in"] = Int(expiresIn.timeIntervalSinceNow)
} }
return params return params
} }
static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = MastodonAPI.dateFormat
return dateFormatter
}()
} }

View file

@ -143,8 +143,12 @@ extension IdentityService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func filtersObservation() -> AnyPublisher<[Filter], Error> { func activeFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> {
contentDatabase.filtersObservation() contentDatabase.activeFiltersObservation(date: date)
}
func expiredFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> {
contentDatabase.expiredFiltersObservation(date: date)
} }
func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Never, Error> { func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Never, Error> {

View file

@ -7,10 +7,12 @@ class EditFilterViewModel: ObservableObject {
@Published var filter: Filter @Published var filter: Filter
@Published var saving = false @Published var saving = false
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
var date: Date
let dateRange: ClosedRange<Date>
let saveCompleted: AnyPublisher<Void, Never> let saveCompleted: AnyPublisher<Void, Never>
var date: Date {
didSet { filter.expiresAt = date }
}
private let identityService: IdentityService private let identityService: IdentityService
private let saveCompletedInput = PassthroughSubject<Void, Never>() private let saveCompletedInput = PassthroughSubject<Void, Never>()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -18,8 +20,7 @@ class EditFilterViewModel: ObservableObject {
init(filter: Filter, identityService: IdentityService) { init(filter: Filter, identityService: IdentityService) {
self.filter = filter self.filter = filter
self.identityService = identityService self.identityService = identityService
date = Calendar.autoupdatingCurrent.date(byAdding: .minute, value: 30, to: Date()) ?? Date() date = filter.expiresAt ?? Date()
dateRange = date...(Calendar.autoupdatingCurrent.date(byAdding: .day, value: 7, to: date) ?? Date())
saveCompleted = saveCompletedInput.eraseToAnyPublisher() saveCompleted = saveCompletedInput.eraseToAnyPublisher()
} }
} }

View file

@ -4,7 +4,8 @@ import Foundation
import Combine import Combine
class FiltersViewModel: ObservableObject { class FiltersViewModel: ObservableObject {
@Published var filters = [Filter]() @Published var activeFilters = [Filter]()
@Published var expiredFilters = [Filter]()
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
private let identityService: IdentityService private let identityService: IdentityService
@ -13,9 +14,15 @@ class FiltersViewModel: ObservableObject {
init(identityService: IdentityService) { init(identityService: IdentityService) {
self.identityService = identityService self.identityService = identityService
identityService.filtersObservation() let now = Date()
identityService.activeFiltersObservation(date: now)
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.assign(to: &$filters) .assign(to: &$activeFilters)
identityService.expiredFiltersObservation(date: now)
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.assign(to: &$expiredFilters)
} }
} }
@ -27,6 +34,13 @@ extension FiltersViewModel {
.store(in: &cancellables) .store(in: &cancellables)
} }
func delete(filter: Filter) {
identityService.deleteFilter(id: filter.id)
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.sink { _ in }
.store(in: &cancellables)
}
func editFilterViewModel(filter: Filter) -> EditFilterViewModel { func editFilterViewModel(filter: Filter) -> EditFilterViewModel {
EditFilterViewModel(filter: filter, identityService: identityService) EditFilterViewModel(filter: filter, identityService: identityService)
} }

View file

@ -16,17 +16,11 @@ struct EditFilterView: View {
if viewModel.isNew || viewModel.filter.expiresAt == nil { if viewModel.isNew || viewModel.filter.expiresAt == nil {
Toggle("filter.never-expires", isOn: .init( Toggle("filter.never-expires", isOn: .init(
get: { viewModel.filter.expiresAt == nil }, get: { viewModel.filter.expiresAt == nil },
set: { set: { viewModel.filter.expiresAt = $0 ? nil : viewModel.date }))
if $0 {
viewModel.filter.expiresAt = nil
} else {
viewModel.filter.expiresAt = viewModel.date
}
}))
} }
if viewModel.filter.expiresAt != nil { if viewModel.filter.expiresAt != nil {
DatePicker(selection: $viewModel.date, in: viewModel.dateRange) { DatePicker(selection: $viewModel.date, in: Date()...) {
Text("filter.expire-after") Text("filter.expire-after")
} }
} }

View file

@ -13,8 +13,26 @@ struct FiltersView: View {
Label("add", systemImage: "plus.circle") Label("add", systemImage: "plus.circle")
} }
} }
Section { section(title: "filters.active", filters: viewModel.activeFilters)
ForEach(viewModel.filters) { filter in section(title: "filters.expired", filters: viewModel.expiredFilters)
}
.navigationTitle("preferences.filters")
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) {
EditButton()
}
}
.alertItem($viewModel.alertItem)
.onAppear(perform: viewModel.refreshFilters)
}
}
private extension FiltersView {
@ViewBuilder
func section(title: LocalizedStringKey, filters: [Filter]) -> some View {
if !filters.isEmpty {
Section(header: Text(title)) {
ForEach(filters) { filter in
NavigationLink(destination: EditFilterView( NavigationLink(destination: EditFilterView(
viewModel: viewModel.editFilterViewModel(filter: filter))) { viewModel: viewModel.editFilterViewModel(filter: filter))) {
HStack { HStack {
@ -25,11 +43,13 @@ struct FiltersView: View {
} }
} }
} }
.onDelete {
guard let index = $0.first else { return }
viewModel.delete(filter: filters[index])
}
} }
} }
.navigationTitle("preferences.filters")
.alertItem($viewModel.alertItem)
.onAppear(perform: viewModel.refreshFilters)
} }
} }