diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index e1f09c3c5..781aba6cd 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -3,6 +3,7 @@ import UIKit import MastodonLocalization import MastodonAsset +import MastodonCore enum NotificationFilterSection: Hashable { case main @@ -49,7 +50,10 @@ struct NotificationFilterViewModel { var newAccount: Bool var privateMentions: Bool - init(notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) { + let appContext: AppContext + + init(appContext: AppContext, notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) { + self.appContext = appContext self.notFollowing = notFollowing self.noFollower = noFollower self.newAccount = newAccount @@ -60,6 +64,7 @@ struct NotificationFilterViewModel { class NotificationPolicyViewController: UIViewController { let tableView: UITableView + var saveItem: UIBarButtonItem? var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] var viewModel: NotificationFilterViewModel @@ -96,7 +101,8 @@ class NotificationPolicyViewController: UIViewController { view.addSubview(tableView) view.backgroundColor = .systemGroupedBackground - navigationItem.rightBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) + saveItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) + navigationItem.rightBarButtonItem = saveItem navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .done, target: self, action: #selector(NotificationPolicyViewController.cancel(_:))) setupConstraints() @@ -129,7 +135,39 @@ class NotificationPolicyViewController: UIViewController { // MARK: - Action @objc private func save(_ sender: UIBarButtonItem) { - //TODO: Save aka PATH viewModel to API and dismiss + guard let authenticationBox = viewModel.appContext.authenticationService.mastodonAuthenticationBoxes.first else { return } + + navigationItem.leftBarButtonItem?.isEnabled = false + + let activityIndicator = UIActivityIndicatorView(style: .medium) + + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activityIndicator) + navigationItem.rightBarButtonItem?.isEnabled = false + + activityIndicator.startAnimating() + + Task { [weak self] in + guard let self else { return } + + do { + let result = try await viewModel.appContext.apiService.updateNotificationPolicy( + authenticationBox: authenticationBox, + filterNotFollowing: viewModel.notFollowing, + filterNotFollowers: viewModel.noFollower, + filterNewAccounts: viewModel.newAccount, + filterPrivateMentions: viewModel.privateMentions + ) + + await MainActor.run { + self.dismiss(animated:true) + } + } catch { + navigationItem.rightBarButtonItem = saveItem + navigationItem.rightBarButtonItem?.isEnabled = true + navigationItem.leftBarButtonItem?.isEnabled = true + } + } + } @objc private func cancel(_ sender: UIBarButtonItem) { diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 12dd976fc..e5a6d6cfa 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -118,9 +118,15 @@ extension NotificationViewController { //MARK: - Actions @objc private func showNotificationPolicySettings(_ sender: Any) { - guard let policy = viewModel?.notificationPolicy else { return } + guard let viewModel, let policy = viewModel.notificationPolicy else { return } - let policyViewModel = NotificationFilterViewModel(notFollowing: policy.filterNotFollowing, noFollower: policy.filterNotFollowers, newAccount: policy.filterNewAccounts, privateMentions: policy.filterPrivateMentions) + let policyViewModel = NotificationFilterViewModel( + appContext: viewModel.context, + notFollowing: policy.filterNotFollowing, + noFollower: policy.filterNotFollowers, + newAccount: policy.filterNewAccounts, + privateMentions: policy.filterPrivateMentions + ) //TODO: Move to SceneCoordinator let notificationPolicyViewController = NotificationPolicyViewController(viewModel: policyViewModel) notificationPolicyViewController.modalPresentationStyle = .formSheet diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index b66e8fe23..4453e8e6a 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -113,7 +113,28 @@ extension APIService { let domain = authenticationBox.domain let authorization = authenticationBox.userAuthorization - let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization).singleOutput() + let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization) + + return response + } + + public func updateNotificationPolicy( + authenticationBox: MastodonAuthenticationBox, + filterNotFollowing: Bool, + filterNotFollowers: Bool, + filterNewAccounts: Bool, + filterPrivateMentions: Bool + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + let query = Mastodon.API.Notifications.UpdateNotificationPolicyQuery(filterNotFollowing: filterNotFollowing, filterNotFollowers: filterNotFollowers, filterNewAccounts: filterNewAccounts, filterPrivateMentions: filterPrivateMentions) + + let response = try await Mastodon.API.Notifications.updateNotificationPolicy( + session: session, + domain: domain, + authorization: authorization, + query: query + ) return response } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index f10740b2a..5df6c8e8d 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -142,20 +142,57 @@ extension Mastodon.API.Notifications { notificationsEndpointURL(domain: domain).appendingPathComponent("policy") } + public struct UpdateNotificationPolicyQuery: Codable, PatchQuery { + public let filterNotFollowing: Bool + public let filterNotFollowers: Bool + public let filterNewAccounts: Bool + public let filterPrivateMentions: Bool + + enum CodingKeys: String, CodingKey { + case filterNotFollowing = "filter_not_following" + case filterNotFollowers = "filter_not_followers" + case filterNewAccounts = "filter_new_accounts" + case filterPrivateMentions = "filter_private_mentions" + } + + public init(filterNotFollowing: Bool, filterNotFollowers: Bool, filterNewAccounts: Bool, filterPrivateMentions: Bool) { + self.filterNotFollowing = filterNotFollowing + self.filterNotFollowers = filterNotFollowers + self.filterNewAccounts = filterNewAccounts + self.filterPrivateMentions = filterPrivateMentions + } + } + public static func getNotificationPolicy( session: URLSession, domain: String, authorization: Mastodon.API.OAuth.Authorization - ) -> AnyPublisher, Error> { + ) async throws -> Mastodon.Response.Content { let request = Mastodon.API.get( url: notificationPolicyEndpointURL(domain: domain), authorization: authorization ) - return session.dataTaskPublisher(for: request) - .tryMap { data, response in - let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) - return Mastodon.Response.Content(value: value, response: response) - } - .eraseToAnyPublisher() + + let (data, response) = try await session.data(for: request) + + let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + + public static func updateNotificationPolicy( + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization, + query: Mastodon.API.Notifications.UpdateNotificationPolicyQuery + ) async throws -> Mastodon.Response.Content { + let request = Mastodon.API.patch( + url: notificationPolicyEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + let (data, response) = try await session.data(for: request) + let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) + + return Mastodon.Response.Content(value: value, response: response) } } diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift index f93d8af1b..caaf6a02a 100644 --- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift +++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift @@ -55,6 +55,11 @@ extension PostQuery { // PATCH protocol PatchQuery: RequestQuery { } +extension PatchQuery { + // By default a `PostQuery` does not have query items + var queryItems: [URLQueryItem]? { nil } +} + // PUT protocol PutQuery: RequestQuery { }