diff --git a/Databases/ContentDatabase.swift b/Databases/ContentDatabase.swift index c755672..8eb294d 100644 --- a/Databases/ContentDatabase.swift +++ b/Databases/ContentDatabase.swift @@ -142,6 +142,20 @@ extension ContentDatabase { .publisher(in: databaseQueue) .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 { diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 3b4d8dc..bc6508d 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -29,6 +29,8 @@ "preferences.notification-types.reblog" = "Reblog"; "preferences.notification-types.mention" = "Mention"; "preferences.notification-types.poll" = "Poll"; +"filters.active" = "Active"; +"filters.expired" = "Expired"; "filter.add-new" = "Add New Filter"; "filter.edit" = "Edit Filter"; "filter.keyword-or-phrase" = "Keyword or phrase"; diff --git a/Networking/Mastodon API/Endpoints/FilterEndpoint.swift b/Networking/Mastodon API/Endpoints/FilterEndpoint.swift index e90ea2f..fe79f5d 100644 --- a/Networking/Mastodon API/Endpoints/FilterEndpoint.swift +++ b/Networking/Mastodon API/Endpoints/FilterEndpoint.swift @@ -78,17 +78,9 @@ private extension FilterEndpoint { "whole_word": wholeWord] if let expiresIn = expiresIn { - params["expires_in"] = Self.dateFormatter.string(from: expiresIn) + params["expires_in"] = Int(expiresIn.timeIntervalSinceNow) } return params } - - static let dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - - dateFormatter.dateFormat = MastodonAPI.dateFormat - - return dateFormatter - }() } diff --git a/Services/IdentityService.swift b/Services/IdentityService.swift index d155538..6cff564 100644 --- a/Services/IdentityService.swift +++ b/Services/IdentityService.swift @@ -143,8 +143,12 @@ extension IdentityService { .eraseToAnyPublisher() } - func filtersObservation() -> AnyPublisher<[Filter], Error> { - contentDatabase.filtersObservation() + func activeFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> { + contentDatabase.activeFiltersObservation(date: date) + } + + func expiredFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> { + contentDatabase.expiredFiltersObservation(date: date) } func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher { diff --git a/View Models/EditFilterViewModel.swift b/View Models/EditFilterViewModel.swift index f762971..543ac2a 100644 --- a/View Models/EditFilterViewModel.swift +++ b/View Models/EditFilterViewModel.swift @@ -7,10 +7,12 @@ class EditFilterViewModel: ObservableObject { @Published var filter: Filter @Published var saving = false @Published var alertItem: AlertItem? - var date: Date - let dateRange: ClosedRange let saveCompleted: AnyPublisher + var date: Date { + didSet { filter.expiresAt = date } + } + private let identityService: IdentityService private let saveCompletedInput = PassthroughSubject() private var cancellables = Set() @@ -18,8 +20,7 @@ class EditFilterViewModel: ObservableObject { init(filter: Filter, identityService: IdentityService) { self.filter = filter self.identityService = identityService - date = Calendar.autoupdatingCurrent.date(byAdding: .minute, value: 30, to: Date()) ?? Date() - dateRange = date...(Calendar.autoupdatingCurrent.date(byAdding: .day, value: 7, to: date) ?? Date()) + date = filter.expiresAt ?? Date() saveCompleted = saveCompletedInput.eraseToAnyPublisher() } } diff --git a/View Models/FiltersViewModel.swift b/View Models/FiltersViewModel.swift index e5883e6..f46cb5f 100644 --- a/View Models/FiltersViewModel.swift +++ b/View Models/FiltersViewModel.swift @@ -4,7 +4,8 @@ import Foundation import Combine class FiltersViewModel: ObservableObject { - @Published var filters = [Filter]() + @Published var activeFilters = [Filter]() + @Published var expiredFilters = [Filter]() @Published var alertItem: AlertItem? private let identityService: IdentityService @@ -13,9 +14,15 @@ class FiltersViewModel: ObservableObject { init(identityService: IdentityService) { self.identityService = identityService - identityService.filtersObservation() + let now = Date() + + identityService.activeFiltersObservation(date: now) .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) } + func delete(filter: Filter) { + identityService.deleteFilter(id: filter.id) + .assignErrorsToAlertItem(to: \.alertItem, on: self) + .sink { _ in } + .store(in: &cancellables) + } + func editFilterViewModel(filter: Filter) -> EditFilterViewModel { EditFilterViewModel(filter: filter, identityService: identityService) } diff --git a/Views/EditFilterView.swift b/Views/EditFilterView.swift index 3273be8..d48f936 100644 --- a/Views/EditFilterView.swift +++ b/Views/EditFilterView.swift @@ -16,17 +16,11 @@ struct EditFilterView: View { if viewModel.isNew || viewModel.filter.expiresAt == nil { Toggle("filter.never-expires", isOn: .init( get: { viewModel.filter.expiresAt == nil }, - set: { - if $0 { - viewModel.filter.expiresAt = nil - } else { - viewModel.filter.expiresAt = viewModel.date - } - })) + set: { viewModel.filter.expiresAt = $0 ? nil : viewModel.date })) } if viewModel.filter.expiresAt != nil { - DatePicker(selection: $viewModel.date, in: viewModel.dateRange) { + DatePicker(selection: $viewModel.date, in: Date()...) { Text("filter.expire-after") } } diff --git a/Views/FiltersView.swift b/Views/FiltersView.swift index aeaca95..badf90f 100644 --- a/Views/FiltersView.swift +++ b/Views/FiltersView.swift @@ -13,8 +13,26 @@ struct FiltersView: View { Label("add", systemImage: "plus.circle") } } - Section { - ForEach(viewModel.filters) { filter in + section(title: "filters.active", filters: viewModel.activeFilters) + 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( viewModel: viewModel.editFilterViewModel(filter: filter))) { 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) } }