mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Update to three-way choice API for notification filtering
This commit is contained in:
parent
c9da109ea3
commit
fa49cd6630
@ -765,7 +765,6 @@
|
||||
"multiple_people_boosted": "%@ boosted:",
|
||||
"multiple_people_favourited": "%@ favorited:",
|
||||
"multiple_people_followed_you": "%@ followed you",
|
||||
"mulitple_people_signed_up": "%@ new signups",
|
||||
"single_name_signed_up": "%@ signed up",
|
||||
"someone_reported_account_for_rule_violation": "Someone reported %@ for rule violation.",
|
||||
"someone_reported_posts_from_account_for_rule_violation": "Someone reported %@ from %@ for rule violation."
|
||||
@ -827,16 +826,34 @@
|
||||
"private_mentions": {
|
||||
"title": "Unsolicited private mentions",
|
||||
"subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender"
|
||||
},
|
||||
"moderated_accounts": {
|
||||
"title": "Moderated accounts",
|
||||
"subtitle": "Limited by server moderators"
|
||||
},
|
||||
"action": {
|
||||
"filter": {
|
||||
"title": "Filter",
|
||||
"subtitle": "Send to filtered notifications inbox"
|
||||
},
|
||||
"accept": {
|
||||
"title": "Accept",
|
||||
"subtitle": "Show in notifications"
|
||||
},
|
||||
"drop": {
|
||||
"title": "Ignore",
|
||||
"subtitle": "Send to the void, never to be seen again"
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_filter": {
|
||||
"reports": {
|
||||
"title": "Admin reports",
|
||||
"subtitle": "Hide reports of spam, rule violations, and other complaints on this instance"
|
||||
"subtitle": "Show reports of spam, rule violations, and other complaints on this instance"
|
||||
},
|
||||
"signups": {
|
||||
"title": "Account signups",
|
||||
"subtitle": "Hide notifications of new accounts created on this instance"
|
||||
"subtitle": "Show notifications of new accounts created on this instance"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -826,16 +826,34 @@
|
||||
"private_mentions": {
|
||||
"title": "Unsolicited private mentions",
|
||||
"subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender"
|
||||
},
|
||||
"moderated_accounts": {
|
||||
"title": "Moderated accounts",
|
||||
"subtitle": "Limited by server moderators"
|
||||
},
|
||||
"action": {
|
||||
"filter": {
|
||||
"title": "Filter",
|
||||
"subtitle": "Send to filtered notifications inbox"
|
||||
},
|
||||
"accept": {
|
||||
"title": "Accept",
|
||||
"subtitle": "Show in notifications"
|
||||
},
|
||||
"drop": {
|
||||
"title": "Ignore",
|
||||
"subtitle": "Send to the void, never to be seen again"
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_filter": {
|
||||
"reports": {
|
||||
"title": "Admin reports",
|
||||
"subtitle": "Hide reports of spam, rule violations, and other complaints on this instance"
|
||||
"subtitle": "Show reports of spam, rule violations, and other complaints on this instance"
|
||||
},
|
||||
"signups": {
|
||||
"title": "Account signups",
|
||||
"subtitle": "Hide notifications of new accounts created on this instance"
|
||||
"subtitle": "Show notifications of new accounts created on this instance"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -70,16 +70,17 @@ class NotificationListViewController: UIHostingController<NotificationListView>
|
||||
if let existingPreferences = await BodegaPersistence.Notifications.currentPreferences(for: user.authentication) {
|
||||
return existingPreferences
|
||||
} else {
|
||||
return AdminNotificationFilterSettings(filterOutReports: false, filterOutSignups: false)
|
||||
return AdminNotificationFilterSettings(forReports: .accept, forSignups: .accept)
|
||||
}
|
||||
}()
|
||||
|
||||
let policyViewModel = await NotificationFilterViewModel(
|
||||
NotificationFilterSettings(
|
||||
notFollowing: policy.filterNotFollowing,
|
||||
noFollower: policy.filterNotFollowers,
|
||||
newAccount: policy.filterNewAccounts,
|
||||
privateMentions: policy.filterPrivateMentions
|
||||
forNotFollowing: policy.forNotFollowing,
|
||||
forNotFollowers: policy.forNotFollowers,
|
||||
forNewAccounts: policy.forNewAccounts,
|
||||
forPrivateMentions: policy.forPrivateMentions,
|
||||
forLimitedAccounts: policy.forLimitedAccounts
|
||||
),
|
||||
adminSettings: adminSettings
|
||||
)
|
||||
|
@ -1,37 +1,127 @@
|
||||
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
import MastodonAsset
|
||||
|
||||
protocol NotificationPolicyFilterTableViewCellDelegate: AnyObject {
|
||||
func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool)
|
||||
func pickerValueChanged(
|
||||
_ tableViewCell: NotificationPolicyFilterTableViewCell,
|
||||
filterItem: NotificationFilterItem,
|
||||
newValue: Mastodon.Entity.NotificationPolicy.NotificationFilterAction)
|
||||
}
|
||||
|
||||
class NotificationPolicyFilterTableViewCell: ToggleTableViewCell {
|
||||
class NotificationPolicyFilterTableViewCell: TrailingButtonTableViewCell
|
||||
{
|
||||
typealias FilterActionOption = Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
|
||||
override class var reuseIdentifier: String {
|
||||
return "NotificationPolicyFilterTableViewCell"
|
||||
}
|
||||
|
||||
private let options:
|
||||
[FilterActionOption] = [
|
||||
.accept, .filter, .drop,
|
||||
]
|
||||
|
||||
var filterItem: NotificationFilterItem?
|
||||
weak var delegate: NotificationPolicyFilterTableViewCellDelegate?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(
|
||||
for: .systemFont(ofSize: 17, weight: .regular))
|
||||
subtitleLabel.textColor = .secondaryLabel
|
||||
subtitleLabel.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||
subtitleLabel.font = UIFontMetrics(forTextStyle: .subheadline)
|
||||
.scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||
}
|
||||
|
||||
toggle.addTarget(self, action: #selector(NotificationPolicyFilterTableViewCell.toggleValueChanged(_:)), for: .valueChanged)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
private func indexForOption(_ option: FilterActionOption) -> Int {
|
||||
return options.firstIndex(of: option) ?? 0
|
||||
}
|
||||
|
||||
public func configure(with filterItem: NotificationFilterItem, viewModel: NotificationFilterViewModel) {
|
||||
public func configure(
|
||||
with filterItem: NotificationFilterItem,
|
||||
viewModel: NotificationFilterViewModel
|
||||
) {
|
||||
label.text = filterItem.title
|
||||
subtitleLabel.text = filterItem.subtitle
|
||||
self.filterItem = filterItem
|
||||
|
||||
let toggleIsOn = viewModel.value(forItem: filterItem)
|
||||
let buttonTitle: String
|
||||
switch viewModel.value(forItem: filterItem) {
|
||||
case .accept: buttonTitle = L10n.Scene.Notification.Policy.Action.Accept.title
|
||||
case .filter: buttonTitle = L10n.Scene.Notification.Policy.Action.Filter.title
|
||||
case .drop: buttonTitle = L10n.Scene.Notification.Policy.Action.Drop.title
|
||||
case ._other(let string): buttonTitle = string
|
||||
}
|
||||
button.configuration = .bordered()
|
||||
button.configuration?.title = buttonTitle
|
||||
button.configuration?.background.strokeColor = Asset.Colors.Brand.blurple.color
|
||||
button.configuration?.baseForegroundColor = Asset.Colors.Brand.blurple.color
|
||||
button.showsMenuAsPrimaryAction = true
|
||||
|
||||
let menuActions = [FilterActionOption.accept, .filter, .drop].map { option in
|
||||
UIAction(title: option.displayTitle, subtitle: option.displaySubtitle, state: viewModel.value(forItem: filterItem) == option ? .on : .off, handler: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.delegate?.pickerValueChanged(self, filterItem: filterItem, newValue: option)
|
||||
})
|
||||
}
|
||||
|
||||
button.menu = UIMenu.init(children: menuActions)
|
||||
}
|
||||
}
|
||||
|
||||
protocol NotificationAdminFilterTableViewCellDelegate: AnyObject {
|
||||
func toggleValueChanged(
|
||||
_ tableViewCell: NotificationAdminFilterTableViewCell,
|
||||
filterItem: NotificationFilterItem,
|
||||
newValue: Bool)
|
||||
}
|
||||
|
||||
class NotificationAdminFilterTableViewCell: ToggleTableViewCell {
|
||||
override class var reuseIdentifier: String {
|
||||
return "NotificationAdminFilterTableViewCell"
|
||||
}
|
||||
|
||||
var filterItem: NotificationFilterItem?
|
||||
weak var delegate: NotificationAdminFilterTableViewCellDelegate?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(
|
||||
for: .systemFont(ofSize: 17, weight: .regular))
|
||||
subtitleLabel.textColor = .secondaryLabel
|
||||
subtitleLabel.font = UIFontMetrics(forTextStyle: .subheadline)
|
||||
.scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||
|
||||
toggle.addTarget(
|
||||
self,
|
||||
action: #selector(
|
||||
NotificationAdminFilterTableViewCell.toggleValueChanged(_:)),
|
||||
for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func configure(
|
||||
with filterItem: NotificationFilterItem,
|
||||
viewModel: NotificationFilterViewModel
|
||||
) {
|
||||
label.text = filterItem.title
|
||||
subtitleLabel.text = filterItem.subtitle
|
||||
self.filterItem = filterItem
|
||||
|
||||
let toggleIsOn = viewModel.value(forItem: filterItem) == .accept
|
||||
|
||||
toggle.isOn = toggleIsOn
|
||||
}
|
||||
@ -39,6 +129,27 @@ class NotificationPolicyFilterTableViewCell: ToggleTableViewCell {
|
||||
@objc func toggleValueChanged(_ sender: UISwitch) {
|
||||
guard let filterItem, let delegate else { return }
|
||||
|
||||
delegate.toggleValueChanged(self, filterItem: filterItem, newValue: sender.isOn)
|
||||
delegate.toggleValueChanged(
|
||||
self, filterItem: filterItem, newValue: sender.isOn)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.NotificationPolicy.NotificationFilterAction {
|
||||
var displayTitle: String {
|
||||
switch self {
|
||||
case .accept: return L10n.Scene.Notification.Policy.Action.Accept.title
|
||||
case .filter: return L10n.Scene.Notification.Policy.Action.Filter.title
|
||||
case .drop: return L10n.Scene.Notification.Policy.Action.Drop.title
|
||||
case ._other(let string): return string
|
||||
}
|
||||
}
|
||||
|
||||
var displaySubtitle: String {
|
||||
switch self {
|
||||
case .accept: return L10n.Scene.Notification.Policy.Action.Accept.subtitle
|
||||
case .filter: return L10n.Scene.Notification.Policy.Action.Filter.subtitle
|
||||
case .drop: return L10n.Scene.Notification.Policy.Action.Drop.subtitle
|
||||
case ._other: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,22 +13,30 @@ enum NotificationFilterSection: Hashable {
|
||||
|
||||
enum NotificationFilterItem: Hashable {
|
||||
case notFollowing
|
||||
case noFollower
|
||||
case newAccount
|
||||
case notFollowers
|
||||
case newAccounts
|
||||
case privateMentions
|
||||
case limitedAccounts
|
||||
|
||||
case adminReports
|
||||
case adminSignups
|
||||
|
||||
static let regularOptions = [Self.notFollowing, .notFollowers, .newAccounts, .privateMentions, .limitedAccounts]
|
||||
static let adminOptions = [Self.adminReports, .adminSignups]
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .notFollowing:
|
||||
return L10n.Scene.Notification.Policy.NotFollowing.title
|
||||
case .noFollower:
|
||||
case .notFollowers:
|
||||
return L10n.Scene.Notification.Policy.NoFollower.title
|
||||
case .newAccount:
|
||||
case .newAccounts:
|
||||
return L10n.Scene.Notification.Policy.NewAccount.title
|
||||
case .privateMentions:
|
||||
return L10n.Scene.Notification.Policy.PrivateMentions.title
|
||||
case .limitedAccounts:
|
||||
return L10n.Scene.Notification.Policy.ModeratedAccounts.title
|
||||
|
||||
case .adminReports:
|
||||
return L10n.Scene.Notification.AdminFilter.Reports.title
|
||||
case .adminSignups:
|
||||
@ -40,12 +48,15 @@ enum NotificationFilterItem: Hashable {
|
||||
switch self {
|
||||
case .notFollowing:
|
||||
return L10n.Scene.Notification.Policy.NotFollowing.subtitle
|
||||
case .noFollower:
|
||||
case .notFollowers:
|
||||
return L10n.Scene.Notification.Policy.NoFollower.subtitle
|
||||
case .newAccount:
|
||||
case .newAccounts:
|
||||
return L10n.Scene.Notification.Policy.NewAccount.subtitle
|
||||
case .privateMentions:
|
||||
return L10n.Scene.Notification.Policy.PrivateMentions.subtitle
|
||||
case .limitedAccounts:
|
||||
return L10n.Scene.Notification.Policy.ModeratedAccounts.subtitle
|
||||
|
||||
case .adminReports:
|
||||
return L10n.Scene.Notification.AdminFilter.Reports.subtitle
|
||||
case .adminSignups:
|
||||
@ -55,21 +66,22 @@ enum NotificationFilterItem: Hashable {
|
||||
}
|
||||
|
||||
struct NotificationFilterSettings: Codable, Equatable {
|
||||
let notFollowing: Bool
|
||||
let noFollower: Bool
|
||||
let newAccount: Bool
|
||||
let privateMentions: Bool
|
||||
let forNotFollowing: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
let forNotFollowers: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
let forNewAccounts: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
let forPrivateMentions: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
let forLimitedAccounts: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
}
|
||||
struct AdminNotificationFilterSettings: Codable, Equatable {
|
||||
let filterOutReports: Bool
|
||||
let filterOutSignups: Bool
|
||||
let forReports: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
let forSignups: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
|
||||
var excludedNotificationTypes: [Mastodon.Entity.NotificationType]? {
|
||||
var excluded = [Mastodon.Entity.NotificationType]()
|
||||
if filterOutReports {
|
||||
if forReports != .accept {
|
||||
excluded.append(.adminReport)
|
||||
}
|
||||
if filterOutSignups {
|
||||
if forSignups != .accept {
|
||||
excluded.append(.adminSignUp)
|
||||
}
|
||||
return excluded.isEmpty ? nil : excluded
|
||||
@ -93,59 +105,73 @@ class NotificationFilterViewModel {
|
||||
self.adminFilterSettings = adminSettings
|
||||
}
|
||||
|
||||
func value(forItem item: NotificationFilterItem) -> Bool {
|
||||
func value(forItem item: NotificationFilterItem) -> Mastodon.Entity.NotificationPolicy.NotificationFilterAction {
|
||||
switch item {
|
||||
case .notFollowing:
|
||||
return regularFilterSettings.notFollowing
|
||||
case .noFollower:
|
||||
return regularFilterSettings.noFollower
|
||||
case .newAccount:
|
||||
return regularFilterSettings.newAccount
|
||||
return regularFilterSettings.forNotFollowing
|
||||
case .notFollowers:
|
||||
return regularFilterSettings.forNotFollowers
|
||||
case .newAccounts:
|
||||
return regularFilterSettings.forNewAccounts
|
||||
case .privateMentions:
|
||||
return regularFilterSettings.privateMentions
|
||||
return regularFilterSettings.forPrivateMentions
|
||||
case .limitedAccounts:
|
||||
return regularFilterSettings.forLimitedAccounts
|
||||
case .adminReports:
|
||||
return adminFilterSettings?.filterOutReports ?? true
|
||||
return adminFilterSettings?.forReports ?? .drop
|
||||
case .adminSignups:
|
||||
return adminFilterSettings?.filterOutSignups ?? true
|
||||
return adminFilterSettings?.forSignups ?? .drop
|
||||
}
|
||||
}
|
||||
|
||||
func setValue(_ value: Bool, forItem item: NotificationFilterItem) {
|
||||
func setValue(_ value: Mastodon.Entity.NotificationPolicy.NotificationFilterAction, forItem item: NotificationFilterItem) {
|
||||
switch item {
|
||||
case .notFollowing:
|
||||
regularFilterSettings = NotificationFilterSettings(
|
||||
notFollowing: value,
|
||||
noFollower: regularFilterSettings.noFollower,
|
||||
newAccount: regularFilterSettings.newAccount,
|
||||
privateMentions: regularFilterSettings.privateMentions)
|
||||
case .noFollower:
|
||||
forNotFollowing: value,
|
||||
forNotFollowers: regularFilterSettings.forNotFollowers,
|
||||
forNewAccounts: regularFilterSettings.forNewAccounts,
|
||||
forPrivateMentions: regularFilterSettings.forPrivateMentions,
|
||||
forLimitedAccounts: regularFilterSettings.forLimitedAccounts)
|
||||
case .notFollowers:
|
||||
regularFilterSettings = NotificationFilterSettings(
|
||||
notFollowing: regularFilterSettings.notFollowing,
|
||||
noFollower: value,
|
||||
newAccount: regularFilterSettings.newAccount,
|
||||
privateMentions: regularFilterSettings.privateMentions)
|
||||
case .newAccount:
|
||||
forNotFollowing: regularFilterSettings.forNotFollowing,
|
||||
forNotFollowers: value,
|
||||
forNewAccounts: regularFilterSettings.forNewAccounts,
|
||||
forPrivateMentions: regularFilterSettings.forPrivateMentions,
|
||||
forLimitedAccounts: regularFilterSettings.forLimitedAccounts)
|
||||
case .newAccounts:
|
||||
regularFilterSettings = NotificationFilterSettings(
|
||||
notFollowing: regularFilterSettings.notFollowing,
|
||||
noFollower: regularFilterSettings.noFollower,
|
||||
newAccount: value,
|
||||
privateMentions: regularFilterSettings.privateMentions)
|
||||
forNotFollowing: regularFilterSettings.forNotFollowing,
|
||||
forNotFollowers: regularFilterSettings.forNotFollowers,
|
||||
forNewAccounts: value,
|
||||
forPrivateMentions: regularFilterSettings.forPrivateMentions,
|
||||
forLimitedAccounts: regularFilterSettings.forLimitedAccounts)
|
||||
case .privateMentions:
|
||||
regularFilterSettings = NotificationFilterSettings(
|
||||
notFollowing: regularFilterSettings.notFollowing,
|
||||
noFollower: regularFilterSettings.noFollower,
|
||||
newAccount: regularFilterSettings.newAccount,
|
||||
privateMentions: value)
|
||||
forNotFollowing: regularFilterSettings.forNotFollowing,
|
||||
forNotFollowers: regularFilterSettings.forNotFollowers,
|
||||
forNewAccounts: regularFilterSettings.forNewAccounts,
|
||||
forPrivateMentions: value,
|
||||
forLimitedAccounts: regularFilterSettings.forLimitedAccounts)
|
||||
case .limitedAccounts:
|
||||
regularFilterSettings = NotificationFilterSettings(
|
||||
forNotFollowing: regularFilterSettings.forNotFollowing,
|
||||
forNotFollowers: regularFilterSettings.forNotFollowers,
|
||||
forNewAccounts: regularFilterSettings.forNewAccounts,
|
||||
forPrivateMentions: regularFilterSettings.forPrivateMentions,
|
||||
forLimitedAccounts: value)
|
||||
|
||||
case .adminReports:
|
||||
guard let adminFilterSettings else { return }
|
||||
self.adminFilterSettings = AdminNotificationFilterSettings(
|
||||
filterOutReports: value,
|
||||
filterOutSignups: adminFilterSettings.filterOutSignups)
|
||||
forReports: value,
|
||||
forSignups: adminFilterSettings.forSignups)
|
||||
case .adminSignups:
|
||||
guard let adminFilterSettings else { return }
|
||||
self.adminFilterSettings = AdminNotificationFilterSettings(
|
||||
filterOutReports: adminFilterSettings.filterOutReports,
|
||||
filterOutSignups: value)
|
||||
forReports: adminFilterSettings.forReports,
|
||||
forSignups: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +198,7 @@ class NotificationPolicyViewController: UIViewController {
|
||||
|
||||
init(viewModel: NotificationFilterViewModel) {
|
||||
self.viewModel = viewModel
|
||||
regularItems = [.notFollowing, .noFollower, .newAccount, .privateMentions]
|
||||
regularItems = [.notFollowing, .notFollowers, .newAccounts, .privateMentions, .limitedAccounts]
|
||||
adminItems = [.adminReports, .adminSignups]
|
||||
|
||||
headerBar = NotificationPolicyHeaderView()
|
||||
@ -182,8 +208,12 @@ class NotificationPolicyViewController: UIViewController {
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(
|
||||
NotificationPolicyFilterTableViewCell.self,
|
||||
forCellReuseIdentifier: NotificationPolicyFilterTableViewCell
|
||||
.reuseIdentifier)
|
||||
forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier
|
||||
)
|
||||
tableView.register(
|
||||
NotificationAdminFilterTableViewCell.self,
|
||||
forCellReuseIdentifier: NotificationAdminFilterTableViewCell.reuseIdentifier
|
||||
)
|
||||
tableView.contentInset.top = -20
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
@ -192,15 +222,14 @@ class NotificationPolicyViewController: UIViewController {
|
||||
NotificationFilterSection, NotificationFilterItem
|
||||
>(tableView: tableView) {
|
||||
[weak self] tableView, indexPath, itemIdentifier in
|
||||
guard let self,
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: NotificationPolicyFilterTableViewCell
|
||||
.reuseIdentifier, for: indexPath)
|
||||
as? NotificationPolicyFilterTableViewCell
|
||||
else {
|
||||
guard let self else {
|
||||
fatalError("No NotificationPolicyFilterTableViewCell")
|
||||
}
|
||||
|
||||
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: indexPath.section == 0 ? NotificationPolicyFilterTableViewCell
|
||||
.reuseIdentifier: NotificationAdminFilterTableViewCell.reuseIdentifier, for: indexPath)
|
||||
|
||||
let item: NotificationFilterItem?
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
@ -212,9 +241,13 @@ class NotificationPolicyViewController: UIViewController {
|
||||
assertionFailure()
|
||||
}
|
||||
guard let item else { return nil }
|
||||
cell.configure(with: item, viewModel: self.viewModel)
|
||||
cell.delegate = self
|
||||
|
||||
if let cell = cell as? NotificationAdminFilterTableViewCell {
|
||||
cell.configure(with: item, viewModel: self.viewModel)
|
||||
cell.delegate = self
|
||||
} else if let cell = cell as? NotificationPolicyFilterTableViewCell {
|
||||
cell.configure(with: item, viewModel: self.viewModel)
|
||||
cell.delegate = self
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
@ -241,7 +274,7 @@ class NotificationPolicyViewController: UIViewController {
|
||||
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(regularItems)
|
||||
if let adminFilterSettings = viewModel.adminFilterSettings {
|
||||
if viewModel.adminFilterSettings != nil {
|
||||
snapshot.appendSections([.admin])
|
||||
snapshot.appendItems(adminItems)
|
||||
}
|
||||
@ -289,10 +322,11 @@ class NotificationPolicyViewController: UIViewController {
|
||||
let updatedPolicy = try await APIService.shared
|
||||
.updateNotificationPolicy(
|
||||
authenticationBox: authenticationBox,
|
||||
filterNotFollowing: viewModel.value(forItem: .notFollowing),
|
||||
filterNotFollowers: viewModel.value(forItem: .noFollower),
|
||||
filterNewAccounts: viewModel.value(forItem: .newAccount),
|
||||
filterPrivateMentions: viewModel.value(forItem: .privateMentions)
|
||||
forNotFollowing: viewModel.value(forItem: .notFollowing),
|
||||
forNotFollowers: viewModel.value(forItem: .notFollowers),
|
||||
forNewAccounts: viewModel.value(forItem: .newAccounts),
|
||||
forPrivateMentions: viewModel.value(forItem: .privateMentions),
|
||||
forLimitedAccounts: viewModel.value(forItem: .limitedAccounts)
|
||||
).value
|
||||
|
||||
delegate?.policyUpdated(self, newPolicy: updatedPolicy)
|
||||
@ -319,19 +353,11 @@ extension NotificationPolicyViewController: UITableViewDelegate {
|
||||
) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let filterItem: NotificationFilterItem? = {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
return regularItems[indexPath.row]
|
||||
case 1:
|
||||
return adminItems[indexPath.row]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
guard indexPath.section == 1 else { return }
|
||||
let filterItem: NotificationFilterItem? = adminItems[indexPath.row]
|
||||
guard let filterItem else { return }
|
||||
let currentValue = viewModel.value(forItem: filterItem)
|
||||
viewModel.setValue(!currentValue, forItem: filterItem)
|
||||
let currentValue = viewModel.value(forItem: filterItem) == .accept
|
||||
setBool(!currentValue, forItem: filterItem)
|
||||
|
||||
if let snapshot = dataSource?.snapshot() {
|
||||
dataSource?.applySnapshotUsingReloadData(snapshot)
|
||||
@ -339,13 +365,28 @@ extension NotificationPolicyViewController: UITableViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationPolicyViewController {
|
||||
func setBool(_ boolValue: Bool, forItem filterItem: NotificationFilterItem) {
|
||||
let option = boolValue ? Mastodon.Entity.NotificationPolicy.NotificationFilterAction.accept : .drop
|
||||
viewModel.setValue(option, forItem: filterItem)
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationPolicyViewController:
|
||||
NotificationPolicyFilterTableViewCellDelegate
|
||||
{
|
||||
func toggleValueChanged(
|
||||
_ tableViewCell: NotificationPolicyFilterTableViewCell,
|
||||
filterItem: NotificationFilterItem, newValue: Bool
|
||||
) {
|
||||
func pickerValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: MastodonSDK.Mastodon.Entity.NotificationPolicy.NotificationFilterAction) {
|
||||
viewModel.setValue(newValue, forItem: filterItem)
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationPolicyViewController : NotificationAdminFilterTableViewCellDelegate {
|
||||
func toggleValueChanged(
|
||||
_ tableViewCell: NotificationAdminFilterTableViewCell,
|
||||
filterItem: NotificationFilterItem, newValue: Bool
|
||||
) {
|
||||
setBool(newValue, forItem: filterItem)
|
||||
}
|
||||
}
|
||||
|
@ -56,3 +56,58 @@ class ToggleTableViewCell: UITableViewCell {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
|
||||
class TrailingButtonTableViewCell: UITableViewCell {
|
||||
class var reuseIdentifier: String {
|
||||
return "TrailingButtonTableViewCell"
|
||||
}
|
||||
|
||||
let label: UILabel
|
||||
let subtitleLabel: UILabel
|
||||
private let labelStackView: UIStackView
|
||||
let button: UIButton
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
|
||||
label = UILabel()
|
||||
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||
label.numberOfLines = 0
|
||||
|
||||
subtitleLabel = UILabel()
|
||||
subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||
subtitleLabel.numberOfLines = 0
|
||||
|
||||
labelStackView = UIStackView(arrangedSubviews: [label, subtitleLabel])
|
||||
labelStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
labelStackView.alignment = .leading
|
||||
labelStackView.axis = .vertical
|
||||
labelStackView.spacing = 4
|
||||
|
||||
let buttonConfiguration = UIButton.Configuration.bordered()
|
||||
button = UIButton.init(configuration: buttonConfiguration)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(labelStackView)
|
||||
contentView.addSubview(button)
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private func setupConstraints() {
|
||||
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
button.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||
let constraints = [
|
||||
labelStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
|
||||
labelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
contentView.bottomAnchor.constraint(equalTo: labelStackView.bottomAnchor, constant: 11),
|
||||
|
||||
button.leadingAnchor.constraint(greaterThanOrEqualTo: labelStackView.trailingAnchor, constant: 16),
|
||||
button.topAnchor.constraint(equalTo: labelStackView.topAnchor),
|
||||
contentView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: 16),
|
||||
]
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
||||
|
@ -136,14 +136,15 @@ extension APIService {
|
||||
|
||||
public func updateNotificationPolicy(
|
||||
authenticationBox: MastodonAuthenticationBox,
|
||||
filterNotFollowing: Bool,
|
||||
filterNotFollowers: Bool,
|
||||
filterNewAccounts: Bool,
|
||||
filterPrivateMentions: Bool
|
||||
forNotFollowing: Mastodon.Entity.NotificationPolicy.NotificationFilterAction,
|
||||
forNotFollowers: Mastodon.Entity.NotificationPolicy.NotificationFilterAction,
|
||||
forNewAccounts: Mastodon.Entity.NotificationPolicy.NotificationFilterAction,
|
||||
forPrivateMentions: Mastodon.Entity.NotificationPolicy.NotificationFilterAction,
|
||||
forLimitedAccounts: Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.NotificationPolicy> {
|
||||
let domain = authenticationBox.domain
|
||||
let authorization = authenticationBox.userAuthorization
|
||||
let query = Mastodon.API.Notifications.UpdateNotificationPolicyQuery(filterNotFollowing: filterNotFollowing, filterNotFollowers: filterNotFollowers, filterNewAccounts: filterNewAccounts, filterPrivateMentions: filterPrivateMentions)
|
||||
let query = Mastodon.API.Notifications.UpdateNotificationPolicyQuery(forNotFollowing: forNotFollowing, forNotFollowers: forNotFollowers, forNewAccounts: forNewAccounts, forPrivateMentions: forPrivateMentions, forLimitedAccounts: forLimitedAccounts)
|
||||
|
||||
let response = try await Mastodon.API.Notifications.updateNotificationPolicy(
|
||||
session: session,
|
||||
|
@ -945,14 +945,14 @@ public enum L10n {
|
||||
public static let viewReport = L10n.tr("Localizable", "Scene.Notification.ViewReport", fallback: "View report")
|
||||
public enum AdminFilter {
|
||||
public enum Reports {
|
||||
/// Hide reports of spam, rule violations, and other complaints
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Reports.Subtitle", fallback: "Hide reports of spam, rule violations, and other complaints")
|
||||
/// Show reports of spam, rule violations, and other complaints
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Reports.Subtitle", fallback: "Show reports of spam, rule violations, and other complaints")
|
||||
/// Admin reports
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Reports.Title", fallback: "Admin reports")
|
||||
}
|
||||
public enum Signups {
|
||||
/// Hide notifications of new accounts created on this instance
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Signups.Subtitle", fallback: "Hide notifications of new accounts created on this instance")
|
||||
/// Show notifications of new accounts created on this instance
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Signups.Subtitle", fallback: "Show notifications of new accounts created on this instance")
|
||||
/// Account signups
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Signups.Title", fallback: "Account signups")
|
||||
}
|
||||
@ -1102,6 +1102,32 @@ public enum L10n {
|
||||
public enum Policy {
|
||||
/// Filter Notifications from…
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Title", fallback: "Filter Notifications from…")
|
||||
public enum Action {
|
||||
public enum Accept {
|
||||
/// Show in notifications
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Accept.Subtitle", fallback: "Show in notifications")
|
||||
/// Accept
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Accept.Title", fallback: "Accept")
|
||||
}
|
||||
public enum Drop {
|
||||
/// Send to the void, never to be seen again
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Drop.Subtitle", fallback: "Send to the void, never to be seen again")
|
||||
/// Ignore
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Drop.Title", fallback: "Ignore")
|
||||
}
|
||||
public enum Filter {
|
||||
/// Send to filtered notifications inbox
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Filter.Subtitle", fallback: "Send to filtered notifications inbox")
|
||||
/// Filter
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Action.Filter.Title", fallback: "Filter")
|
||||
}
|
||||
}
|
||||
public enum ModeratedAccounts {
|
||||
/// Limited by server moderators
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.ModeratedAccounts.Subtitle", fallback: "Limited by server moderators")
|
||||
/// Moderated accounts
|
||||
public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.ModeratedAccounts.Title", fallback: "Moderated accounts")
|
||||
}
|
||||
public enum NewAccount {
|
||||
/// Created within the past 30 days
|
||||
public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NewAccount.Subtitle", fallback: "Created within the past 30 days")
|
||||
|
@ -391,11 +391,19 @@ Please retry in a few minutes.";
|
||||
"Scene.Notification.Policy.NotFollowing.Title" = "People you don't follow";
|
||||
"Scene.Notification.Policy.PrivateMentions.Subtitle" = "Filtered unless it’s in reply to your own mention or if you follow the sender";
|
||||
"Scene.Notification.Policy.PrivateMentions.Title" = "Unsolicited private mentions";
|
||||
"Scene.Notification.Policy.ModeratedAccounts.Title" = "Moderated accounts";
|
||||
"Scene.Notification.Policy.ModeratedAccounts.Subtitle" = "Limited by server moderators";
|
||||
"Scene.Notification.Policy.Action.Accept.Title" = "Accept";
|
||||
"Scene.Notification.Policy.Action.Accept.Subtitle" = "Show in notifications";
|
||||
"Scene.Notification.Policy.Action.Filter.Title" = "Filter";
|
||||
"Scene.Notification.Policy.Action.Filter.Subtitle" = "Send to filtered notifications inbox";
|
||||
"Scene.Notification.Policy.Action.Drop.Title" = "Ignore";
|
||||
"Scene.Notification.Policy.Action.Drop.Subtitle" = "Send to the void, never to be seen again";
|
||||
"Scene.Notification.Policy.Title" = "Filter Notifications from…";
|
||||
"Scene.Notification.AdminFilter.Reports.Title" = "Admin reports";
|
||||
"Scene.Notification.AdminFilter.Reports.Subtitle" = "Hide reports of spam, rule violations, and other complaints";
|
||||
"Scene.Notification.AdminFilter.Reports.Subtitle" = "Show reports of spam, rule violations, and other complaints";
|
||||
"Scene.Notification.AdminFilter.Signups.Title" = "Account signups";
|
||||
"Scene.Notification.AdminFilter.Signups.Subtitle" = "Hide notifications of new accounts created on this instance";
|
||||
"Scene.Notification.AdminFilter.Signups.Subtitle" = "Show notifications of new accounts created on this instance";
|
||||
"Scene.Notification.Title.Everything" = "Everything";
|
||||
"Scene.Notification.Title.Mentions" = "Mentions";
|
||||
"Scene.Notification.Warning.DeleteStatuses" = "Some of your posts have been removed.";
|
||||
|
@ -9,18 +9,25 @@ import Combine
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.API.Notifications {
|
||||
internal static func notificationsEndpointURL(domain: String, grouped: Bool = false) -> URL {
|
||||
internal static func notificationsEndpointURL(
|
||||
domain: String, grouped: Bool = false
|
||||
) -> URL {
|
||||
if grouped {
|
||||
Mastodon.API.endpointV2URL(domain: domain).appendingPathComponent("notifications")
|
||||
Mastodon.API.endpointV2URL(domain: domain).appendingPathComponent(
|
||||
"notifications")
|
||||
} else {
|
||||
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("notifications")
|
||||
Mastodon.API.endpointURL(domain: domain).appendingPathComponent(
|
||||
"notifications")
|
||||
}
|
||||
}
|
||||
|
||||
internal static func getNotificationEndpointURL(domain: String, notificationID: String) -> URL {
|
||||
notificationsEndpointURL(domain: domain).appendingPathComponent(notificationID)
|
||||
internal static func getNotificationEndpointURL(
|
||||
domain: String, notificationID: String
|
||||
) -> URL {
|
||||
notificationsEndpointURL(domain: domain).appendingPathComponent(
|
||||
notificationID)
|
||||
}
|
||||
|
||||
|
||||
/// Get all grouped notifications
|
||||
///
|
||||
/// - Since: 4.3.0
|
||||
@ -47,10 +54,12 @@ extension Mastodon.API.Notifications {
|
||||
authorization: authorization
|
||||
)
|
||||
let (data, response) = try await session.data(for: request)
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.GroupedNotificationsResults.self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: Mastodon.Entity.GroupedNotificationsResults.self, from: data,
|
||||
response: response)
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
/// Get all notifications
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
@ -70,7 +79,9 @@ extension Mastodon.API.Notifications {
|
||||
domain: String,
|
||||
query: Mastodon.API.Notifications.Query,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error> {
|
||||
) -> AnyPublisher<
|
||||
Mastodon.Response.Content<[Mastodon.Entity.Notification]>, Error
|
||||
> {
|
||||
let request = Mastodon.API.get(
|
||||
url: notificationsEndpointURL(domain: domain, grouped: false),
|
||||
query: query,
|
||||
@ -78,12 +89,15 @@ extension Mastodon.API.Notifications {
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Notification].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: [Mastodon.Entity.Notification].self, from: data,
|
||||
response: response)
|
||||
return Mastodon.Response.Content(
|
||||
value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
/// Get a single notification
|
||||
///
|
||||
/// - Since: 0.0.0
|
||||
@ -103,16 +117,22 @@ extension Mastodon.API.Notifications {
|
||||
domain: String,
|
||||
notificationID: Mastodon.Entity.Notification.ID,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Notification>, Error> {
|
||||
) -> AnyPublisher<
|
||||
Mastodon.Response.Content<Mastodon.Entity.Notification>, Error
|
||||
> {
|
||||
let request = Mastodon.API.get(
|
||||
url: getNotificationEndpointURL(domain: domain, notificationID: notificationID),
|
||||
url: getNotificationEndpointURL(
|
||||
domain: domain, notificationID: notificationID),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Notification.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: Mastodon.Entity.Notification.self, from: data,
|
||||
response: response)
|
||||
return Mastodon.Response.Content(
|
||||
value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
@ -127,7 +147,7 @@ extension Mastodon.API.Notifications {
|
||||
public let types: [Mastodon.Entity.NotificationType]?
|
||||
public let excludeTypes: [Mastodon.Entity.NotificationType]?
|
||||
public let accountID: String?
|
||||
|
||||
|
||||
public init(
|
||||
maxID: Mastodon.Entity.Status.ID? = nil,
|
||||
sinceID: Mastodon.Entity.Status.ID? = nil,
|
||||
@ -145,29 +165,42 @@ extension Mastodon.API.Notifications {
|
||||
self.excludeTypes = excludeTypes
|
||||
self.accountID = accountID
|
||||
}
|
||||
|
||||
|
||||
var queryItems: [URLQueryItem]? {
|
||||
var items: [URLQueryItem] = []
|
||||
maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
|
||||
sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
|
||||
minID.flatMap { items.append(URLQueryItem(name: "min_id", value: $0)) }
|
||||
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
|
||||
maxID.flatMap {
|
||||
items.append(URLQueryItem(name: "max_id", value: $0))
|
||||
}
|
||||
sinceID.flatMap {
|
||||
items.append(URLQueryItem(name: "since_id", value: $0))
|
||||
}
|
||||
minID.flatMap {
|
||||
items.append(URLQueryItem(name: "min_id", value: $0))
|
||||
}
|
||||
limit.flatMap {
|
||||
items.append(URLQueryItem(name: "limit", value: String($0)))
|
||||
}
|
||||
if let types = types {
|
||||
types.forEach {
|
||||
items.append(URLQueryItem(name: "types[]", value: $0.rawValue))
|
||||
items.append(
|
||||
URLQueryItem(name: "types[]", value: $0.rawValue))
|
||||
}
|
||||
}
|
||||
if let excludeTypes = excludeTypes {
|
||||
excludeTypes.forEach {
|
||||
items.append(URLQueryItem(name: "exclude_types[]", value: $0.rawValue))
|
||||
items.append(
|
||||
URLQueryItem(
|
||||
name: "exclude_types[]", value: $0.rawValue))
|
||||
}
|
||||
}
|
||||
accountID.flatMap { items.append(URLQueryItem(name: "account_id", value: $0)) }
|
||||
accountID.flatMap {
|
||||
items.append(URLQueryItem(name: "account_id", value: $0))
|
||||
}
|
||||
guard !items.isEmpty else { return nil }
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct GroupedQuery: PagedQueryType, GetQuery {
|
||||
public let maxID: Mastodon.Entity.Status.ID?
|
||||
public let sinceID: Mastodon.Entity.Status.ID?
|
||||
@ -178,7 +211,7 @@ extension Mastodon.API.Notifications {
|
||||
public let accountID: String?
|
||||
public let groupedTypes: [String]?
|
||||
public let expandAccounts: Bool
|
||||
|
||||
|
||||
public init(
|
||||
maxID: Mastodon.Entity.Status.ID? = nil,
|
||||
sinceID: Mastodon.Entity.Status.ID? = nil,
|
||||
@ -200,29 +233,45 @@ extension Mastodon.API.Notifications {
|
||||
self.groupedTypes = groupedTypes
|
||||
self.expandAccounts = expandAccounts
|
||||
}
|
||||
|
||||
|
||||
var queryItems: [URLQueryItem]? {
|
||||
var items: [URLQueryItem] = []
|
||||
maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
|
||||
sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
|
||||
minID.flatMap { items.append(URLQueryItem(name: "min_id", value: $0)) }
|
||||
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
|
||||
maxID.flatMap {
|
||||
items.append(URLQueryItem(name: "max_id", value: $0))
|
||||
}
|
||||
sinceID.flatMap {
|
||||
items.append(URLQueryItem(name: "since_id", value: $0))
|
||||
}
|
||||
minID.flatMap {
|
||||
items.append(URLQueryItem(name: "min_id", value: $0))
|
||||
}
|
||||
limit.flatMap {
|
||||
items.append(URLQueryItem(name: "limit", value: String($0)))
|
||||
}
|
||||
if let types = types {
|
||||
types.forEach {
|
||||
items.append(URLQueryItem(name: "types[]", value: $0.rawValue))
|
||||
items.append(
|
||||
URLQueryItem(name: "types[]", value: $0.rawValue))
|
||||
}
|
||||
}
|
||||
if let excludeTypes = excludeTypes {
|
||||
excludeTypes.forEach {
|
||||
items.append(URLQueryItem(name: "exclude_types[]", value: $0.rawValue))
|
||||
items.append(
|
||||
URLQueryItem(
|
||||
name: "exclude_types[]", value: $0.rawValue))
|
||||
}
|
||||
}
|
||||
accountID.flatMap { items.append(URLQueryItem(name: "account_id", value: $0)) }
|
||||
accountID.flatMap {
|
||||
items.append(URLQueryItem(name: "account_id", value: $0))
|
||||
}
|
||||
// TODO: implement groupedTypes
|
||||
// if let groupedTypes {
|
||||
// items.append(URLQueryItem(name: "grouped_types", value: groupedTypes))
|
||||
// }
|
||||
items.append(URLQueryItem(name: "expand_accounts", value: expandAccounts ? "full" : "partial_avatars"))
|
||||
// if let groupedTypes {
|
||||
// items.append(URLQueryItem(name: "grouped_types", value: groupedTypes))
|
||||
// }
|
||||
items.append(
|
||||
URLQueryItem(
|
||||
name: "expand_accounts",
|
||||
value: expandAccounts ? "full" : "partial_avatars"))
|
||||
guard !items.isEmpty else { return nil }
|
||||
return items
|
||||
}
|
||||
@ -233,27 +282,48 @@ extension Mastodon.API.Notifications {
|
||||
|
||||
extension Mastodon.API.Notifications {
|
||||
internal static func notificationPolicyEndpointURL(domain: String) -> URL {
|
||||
notificationsEndpointURL(domain: domain).appendingPathComponent("policy")
|
||||
return Mastodon.API.endpointV2URL(domain: domain)
|
||||
.appendingPathComponent("notifications").appendingPathComponent(
|
||||
"policy")
|
||||
}
|
||||
|
||||
public struct UpdateNotificationPolicyQuery: Codable, PatchQuery {
|
||||
public let filterNotFollowing: Bool
|
||||
public let filterNotFollowers: Bool
|
||||
public let filterNewAccounts: Bool
|
||||
public let filterPrivateMentions: Bool
|
||||
public let forNotFollowing:
|
||||
Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
public let forNotFollowers:
|
||||
Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
public let forNewAccounts:
|
||||
Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
public let forPrivateMentions:
|
||||
Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
public let forLimitedAccounts:
|
||||
Mastodon.Entity.NotificationPolicy.NotificationFilterAction
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case filterNotFollowing = "filter_not_following"
|
||||
case filterNotFollowers = "filter_not_followers"
|
||||
case filterNewAccounts = "filter_new_accounts"
|
||||
case filterPrivateMentions = "filter_private_mentions"
|
||||
case forNotFollowing = "for_not_following"
|
||||
case forNotFollowers = "for_not_followers"
|
||||
case forNewAccounts = "for_new_accounts"
|
||||
case forPrivateMentions = "for_private_mentions"
|
||||
case forLimitedAccounts = "for_Limited_accounts"
|
||||
}
|
||||
|
||||
public init(filterNotFollowing: Bool, filterNotFollowers: Bool, filterNewAccounts: Bool, filterPrivateMentions: Bool) {
|
||||
self.filterNotFollowing = filterNotFollowing
|
||||
self.filterNotFollowers = filterNotFollowers
|
||||
self.filterNewAccounts = filterNewAccounts
|
||||
self.filterPrivateMentions = filterPrivateMentions
|
||||
public init(
|
||||
forNotFollowing: Mastodon.Entity.NotificationPolicy
|
||||
.NotificationFilterAction,
|
||||
forNotFollowers: Mastodon.Entity.NotificationPolicy
|
||||
.NotificationFilterAction,
|
||||
forNewAccounts: Mastodon.Entity.NotificationPolicy
|
||||
.NotificationFilterAction,
|
||||
forPrivateMentions: Mastodon.Entity.NotificationPolicy
|
||||
.NotificationFilterAction,
|
||||
forLimitedAccounts: Mastodon.Entity.NotificationPolicy
|
||||
.NotificationFilterAction
|
||||
) {
|
||||
self.forNotFollowing = forNotFollowing
|
||||
self.forNotFollowers = forNotFollowers
|
||||
self.forNewAccounts = forNewAccounts
|
||||
self.forPrivateMentions = forPrivateMentions
|
||||
self.forLimitedAccounts = forLimitedAccounts
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,7 +331,9 @@ extension Mastodon.API.Notifications {
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.NotificationPolicy> {
|
||||
) async throws
|
||||
-> Mastodon.Response.Content<Mastodon.Entity.NotificationPolicy>
|
||||
{
|
||||
let request = Mastodon.API.get(
|
||||
url: notificationPolicyEndpointURL(domain: domain),
|
||||
authorization: authorization
|
||||
@ -269,7 +341,9 @@ extension Mastodon.API.Notifications {
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: Mastodon.Entity.NotificationPolicy.self, from: data,
|
||||
response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
|
||||
@ -278,41 +352,58 @@ extension Mastodon.API.Notifications {
|
||||
domain: String,
|
||||
authorization: Mastodon.API.OAuth.Authorization,
|
||||
query: Mastodon.API.Notifications.UpdateNotificationPolicyQuery
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.NotificationPolicy> {
|
||||
) async throws
|
||||
-> Mastodon.Response.Content<Mastodon.Entity.NotificationPolicy>
|
||||
{
|
||||
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)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: Mastodon.Entity.NotificationPolicy.self, from: data,
|
||||
response: response)
|
||||
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.API.Notifications {
|
||||
internal static func notificationRequestsEndpointURL(domain: String) -> URL {
|
||||
notificationsEndpointURL(domain: domain).appendingPathComponent("requests")
|
||||
internal static func notificationRequestsEndpointURL(domain: String) -> URL
|
||||
{
|
||||
notificationsEndpointURL(domain: domain).appendingPathComponent(
|
||||
"requests")
|
||||
}
|
||||
|
||||
internal static func notificationRequestEndpointURL(domain: String, id: String) -> URL {
|
||||
notificationRequestsEndpointURL(domain: domain).appendingPathComponent(id)
|
||||
internal static func notificationRequestEndpointURL(
|
||||
domain: String, id: String
|
||||
) -> URL {
|
||||
notificationRequestsEndpointURL(domain: domain).appendingPathComponent(
|
||||
id)
|
||||
}
|
||||
|
||||
internal static func acceptNotificationRequestEndpointURL(domain: String, id: String) -> URL {
|
||||
notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("accept")
|
||||
internal static func acceptNotificationRequestEndpointURL(
|
||||
domain: String, id: String
|
||||
) -> URL {
|
||||
notificationRequestEndpointURL(domain: domain, id: id)
|
||||
.appendingPathComponent("accept")
|
||||
}
|
||||
|
||||
internal static func dismissNotificationRequestEndpointURL(domain: String, id: String) -> URL {
|
||||
notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("dismiss")
|
||||
internal static func dismissNotificationRequestEndpointURL(
|
||||
domain: String, id: String
|
||||
) -> URL {
|
||||
notificationRequestEndpointURL(domain: domain, id: id)
|
||||
.appendingPathComponent("dismiss")
|
||||
}
|
||||
|
||||
public static func getNotificationRequests(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]> {
|
||||
) async throws
|
||||
-> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]>
|
||||
{
|
||||
let request = Mastodon.API.get(
|
||||
url: notificationRequestsEndpointURL(domain: domain),
|
||||
authorization: authorization
|
||||
@ -320,7 +411,9 @@ extension Mastodon.API.Notifications {
|
||||
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.NotificationRequest].self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: [Mastodon.Entity.NotificationRequest].self, from: data,
|
||||
response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
|
||||
@ -338,7 +431,8 @@ extension Mastodon.API.Notifications {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
// we expect an empty dictionary
|
||||
let value = try Mastodon.API.decode(type: [String: String].self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: [String: String].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
|
||||
@ -356,7 +450,8 @@ extension Mastodon.API.Notifications {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
|
||||
// we expect an empty dictionary
|
||||
let value = try Mastodon.API.decode(type: [String: String].self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(
|
||||
type: [String: String].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
}
|
||||
|
@ -4,28 +4,57 @@ import Foundation
|
||||
|
||||
extension Mastodon.Entity {
|
||||
public struct NotificationPolicy: Codable, Hashable {
|
||||
public let filterNotFollowing: Bool
|
||||
public let filterNotFollowers: Bool
|
||||
public let filterNewAccounts: Bool
|
||||
public let filterPrivateMentions: Bool
|
||||
public let forNotFollowing: NotificationFilterAction
|
||||
public let forNotFollowers: NotificationFilterAction
|
||||
public let forNewAccounts: NotificationFilterAction
|
||||
public let forPrivateMentions: NotificationFilterAction
|
||||
public let forLimitedAccounts: NotificationFilterAction
|
||||
public let summary: Summary
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case filterNotFollowing = "filter_not_following"
|
||||
case filterNotFollowers = "filter_not_followers"
|
||||
case filterNewAccounts = "filter_new_accounts"
|
||||
case filterPrivateMentions = "filter_private_mentions"
|
||||
case forNotFollowing = "for_not_following"
|
||||
case forNotFollowers = "for_not_followers"
|
||||
case forNewAccounts = "for_new_accounts"
|
||||
case forPrivateMentions = "for_private_mentions"
|
||||
case forLimitedAccounts = "for_limited_accounts"
|
||||
case summary
|
||||
}
|
||||
|
||||
|
||||
public struct Summary: Codable, Hashable {
|
||||
public let pendingRequestsCount: Int
|
||||
public let pendingNotificationsCount: Int
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case pendingRequestsCount = "pending_requests_count"
|
||||
case pendingNotificationsCount = "pending_notifications_count"
|
||||
}
|
||||
}
|
||||
|
||||
public enum NotificationFilterAction: RawRepresentable, Codable, Sendable, Equatable, Hashable {
|
||||
public typealias RawValue = String
|
||||
|
||||
case accept
|
||||
case filter
|
||||
case drop
|
||||
case _other(String)
|
||||
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "accept": self = .accept
|
||||
case "filter": self = .filter
|
||||
case "drop": self = .drop
|
||||
default: self = ._other(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: RawValue {
|
||||
switch self {
|
||||
case .accept: return "accept"
|
||||
case .filter: return "filter"
|
||||
case .drop: return "drop"
|
||||
case ._other(let string): return string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user