2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Add option to filter out admin notifications

This commit is contained in:
shannon 2025-04-03 14:50:36 -04:00
parent d345a6872a
commit 8153c16932
13 changed files with 295 additions and 93 deletions

View File

@ -765,6 +765,7 @@
"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,6 +828,16 @@
"title": "Unsolicited private mentions",
"subtitle": "Filtered unless its in reply to your own mention or if you follow the sender"
}
},
"admin_filter": {
"reports": {
"title": "Admin reports",
"subtitle": "Hide 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"
}
}
},
"thread": {

View File

@ -827,6 +827,16 @@
"title": "Unsolicited private mentions",
"subtitle": "Filtered unless its in reply to your own mention or if you follow the sender"
}
},
"admin_filter": {
"reports": {
"title": "Admin reports",
"subtitle": "Hide 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"
}
}
},
"thread": {

View File

@ -513,6 +513,7 @@
FB7C4CCC2CD55DEB00F6129A /* NavigationFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7C4CCB2CD55DEB00F6129A /* NavigationFlow.swift */; };
FB7C4CCE2CD55DFF00F6129A /* NewDonationNavigationFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7C4CCD2CD55DFE00F6129A /* NewDonationNavigationFlow.swift */; };
FB91184C2D9EB96E003F410B /* Bodega in Frameworks */ = {isa = PBXBuildFile; productRef = FB91184B2D9EB96E003F410B /* Bodega */; };
FB91188D2D9EECE7003F410B /* BodegaPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB91188C2D9EC208003F410B /* BodegaPersistence.swift */; };
FBD689B52CCBF0AC00CE29F3 /* DonationCampaignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD689B42CCBF09F00CE29F3 /* DonationCampaignViewModel.swift */; };
/* End PBXBuildFile section */
@ -1269,6 +1270,7 @@
FB8522712CEE302300BA2757 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/WidgetExtension.strings; sourceTree = "<group>"; };
FB8522722CEE302300BA2757 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
FB8522732CEE302300BA2757 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hu; path = hu.lproj/Intents.stringsdict; sourceTree = "<group>"; };
FB91188C2D9EC208003F410B /* BodegaPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodegaPersistence.swift; sourceTree = "<group>"; };
FBD689B42CCBF09F00CE29F3 /* DonationCampaignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationCampaignViewModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -2214,6 +2216,7 @@
DB427DD425BAA00100D1B89D /* Mastodon */ = {
isa = PBXGroup;
children = (
FB91188C2D9EC208003F410B /* BodegaPersistence.swift */,
FBBEA04E2D380FC70000A900 /* In Progress New Layout and Datamodel */,
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
DB427DE325BAA00100D1B89D /* Info.plist */,
@ -3818,6 +3821,7 @@
2A4C3B5D2C579CFB008DD42B /* NotificationRequestConstants.swift in Sources */,
DB4AA6B327BA34B6009EC082 /* CellFrameCacheContainer.swift in Sources */,
DB0FCB942797E2B0006C02E2 /* SearchResultViewModel+Diffable.swift in Sources */,
FB91188D2D9EECE7003F410B /* BodegaPersistence.swift in Sources */,
DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */,
DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */,
D8F9170B2A4B2C80008A5370 /* AboutSettings.swift in Sources */,

View File

@ -0,0 +1,24 @@
// Copyright © 2025 Mastodon gGmbH. All rights reserved.
import Bodega
import MastodonCore
/// Cache user data in a local database.
/// MAKE SURE TO UPDATE removeUser() WHEN ADDING ADDITIONAL CACHES
public class BodegaPersistence {
private static let adminNotificationPreferenceStore = ObjectStorage<AdminNotificationFilterSettings>(storage: SQLiteStorageEngine(directory: .documents(appendingPath: "AdminNotificationPreferences"))!)
public static func removeUser(_ userID: UserIdentifier) async throws {
try await adminNotificationPreferenceStore.removeObject(forKey: CacheKey(userID.globallyUniqueUserIdentifier))
}
public struct Notifications {
static func currentPreferences(for userID: UserIdentifier) async -> AdminNotificationFilterSettings? {
return await adminNotificationPreferenceStore.object(forKey: CacheKey(userID.globallyUniqueUserIdentifier))
}
static func updatePreferences(_ preferences: AdminNotificationFilterSettings, for userID: UserIdentifier) async throws {
try await adminNotificationPreferenceStore.store(preferences, forKey: CacheKey(userID.globallyUniqueUserIdentifier))
}
}
}

View File

@ -344,8 +344,9 @@ extension GroupedNotificationFeedLoader {
.currentActiveUser.value
else { throw APIService.APIError.implicit(.authenticationMissing) }
let adminFilterPreferences = await BodegaPersistence.Notifications.currentPreferences(for: authenticationBox)
let results = try await APIService.shared.groupedNotifications(
olderThan: maxID, newerThan: minID, fromAccount: nil, scope: scope,
olderThan: maxID, newerThan: minID, fromAccount: nil, scope: scope, excludingAdminTypes: adminFilterPreferences?.excludedNotificationTypes,
authenticationBox: authenticationBox
)

View File

@ -62,11 +62,25 @@ class NotificationListViewController: UIHostingController<NotificationListView>
@objc private func showNotificationPolicySettings(_ sender: Any) {
guard let policy = viewModel.filteredNotificationsViewModel.policy else { return }
Task {
let adminSettings: AdminNotificationFilterSettings? = await {
guard let user = AuthenticationServiceProvider.shared.currentActiveUser.value, let role = user.cachedAccount?.role else { print("no role"); return nil }
let hasAdminPermissions = role.hasPermissions(.administrator) || role.hasPermissions(.manageReports) || role.hasPermissions(.manageUsers)
guard hasAdminPermissions else { print("no permissions"); return nil }
if let existingPreferences = await BodegaPersistence.Notifications.currentPreferences(for: user.authentication) {
return existingPreferences
} else {
return AdminNotificationFilterSettings(filterOutReports: false, filterOutSignups: false)
}
}()
let policyViewModel = await NotificationFilterViewModel(
notFollowing: policy.filterNotFollowing,
noFollower: policy.filterNotFollowers,
newAccount: policy.filterNewAccounts,
privateMentions: policy.filterPrivateMentions
NotificationFilterSettings(
notFollowing: policy.filterNotFollowing,
noFollower: policy.filterNotFollowers,
newAccount: policy.filterNewAccounts,
privateMentions: policy.filterPrivateMentions
),
adminSettings: adminSettings
)
guard let policyViewController = self.sceneCoordinator?.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return }

View File

@ -31,17 +31,7 @@ class NotificationPolicyFilterTableViewCell: ToggleTableViewCell {
subtitleLabel.text = filterItem.subtitle
self.filterItem = filterItem
let toggleIsOn: Bool
switch filterItem {
case .notFollowing:
toggleIsOn = viewModel.notFollowing
case .noFollower:
toggleIsOn = viewModel.noFollower
case .newAccount:
toggleIsOn = viewModel.newAccount
case .privateMentions:
toggleIsOn = viewModel.privateMentions
}
let toggleIsOn = viewModel.value(forItem: filterItem)
toggle.isOn = toggleIsOn
}

View File

@ -1,20 +1,23 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
import UIKit
import MastodonLocalization
import MastodonAsset
import MastodonCore
import MastodonLocalization
import MastodonSDK
import UIKit
enum NotificationFilterSection: Hashable {
case main
case admin
}
enum NotificationFilterItem: Hashable, CaseIterable {
enum NotificationFilterItem: Hashable {
case notFollowing
case noFollower
case newAccount
case privateMentions
case adminReports
case adminSignups
var title: String {
switch self {
@ -26,6 +29,10 @@ enum NotificationFilterItem: Hashable, CaseIterable {
return L10n.Scene.Notification.Policy.NewAccount.title
case .privateMentions:
return L10n.Scene.Notification.Policy.PrivateMentions.title
case .adminReports:
return L10n.Scene.Notification.AdminFilter.Reports.title
case .adminSignups:
return L10n.Scene.Notification.AdminFilter.Signups.title
}
}
@ -39,29 +46,114 @@ enum NotificationFilterItem: Hashable, CaseIterable {
return L10n.Scene.Notification.Policy.NewAccount.subtitle
case .privateMentions:
return L10n.Scene.Notification.Policy.PrivateMentions.subtitle
case .adminReports:
return L10n.Scene.Notification.AdminFilter.Reports.subtitle
case .adminSignups:
return L10n.Scene.Notification.AdminFilter.Signups.subtitle
}
}
}
struct NotificationFilterViewModel {
var notFollowing: Bool
var noFollower: Bool
var newAccount: Bool
var privateMentions: Bool
struct NotificationFilterSettings: Codable, Equatable {
let notFollowing: Bool
let noFollower: Bool
let newAccount: Bool
let privateMentions: Bool
}
struct AdminNotificationFilterSettings: Codable, Equatable {
let filterOutReports: Bool
let filterOutSignups: Bool
var excludedNotificationTypes: [Mastodon.Entity.NotificationType]? {
var excluded = [Mastodon.Entity.NotificationType]()
if filterOutReports {
excluded.append(.adminReport)
}
if filterOutSignups {
excluded.append(.adminSignUp)
}
return excluded.isEmpty ? nil : excluded
}
}
let appContext: AppContext
class NotificationFilterViewModel {
let originalRegularSettings: NotificationFilterSettings
let originalAdminSettings: AdminNotificationFilterSettings?
init(notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) async {
self.notFollowing = notFollowing
self.noFollower = noFollower
self.newAccount = newAccount
self.privateMentions = privateMentions
self.appContext = await AppContext.shared
var regularFilterSettings: NotificationFilterSettings
var adminFilterSettings: AdminNotificationFilterSettings?
init(
_ regularSettings: NotificationFilterSettings,
adminSettings: AdminNotificationFilterSettings?
) async {
self.originalRegularSettings = regularSettings
self.regularFilterSettings = regularSettings
self.originalAdminSettings = adminSettings
self.adminFilterSettings = adminSettings
}
func value(forItem item: NotificationFilterItem) -> Bool {
switch item {
case .notFollowing:
return regularFilterSettings.notFollowing
case .noFollower:
return regularFilterSettings.noFollower
case .newAccount:
return regularFilterSettings.newAccount
case .privateMentions:
return regularFilterSettings.privateMentions
case .adminReports:
return adminFilterSettings?.filterOutReports ?? true
case .adminSignups:
return adminFilterSettings?.filterOutSignups ?? true
}
}
func setValue(_ value: Bool, forItem item: NotificationFilterItem) {
switch item {
case .notFollowing:
regularFilterSettings = NotificationFilterSettings(
notFollowing: value,
noFollower: regularFilterSettings.noFollower,
newAccount: regularFilterSettings.newAccount,
privateMentions: regularFilterSettings.privateMentions)
case .noFollower:
regularFilterSettings = NotificationFilterSettings(
notFollowing: regularFilterSettings.notFollowing,
noFollower: value,
newAccount: regularFilterSettings.newAccount,
privateMentions: regularFilterSettings.privateMentions)
case .newAccount:
regularFilterSettings = NotificationFilterSettings(
notFollowing: regularFilterSettings.notFollowing,
noFollower: regularFilterSettings.noFollower,
newAccount: value,
privateMentions: regularFilterSettings.privateMentions)
case .privateMentions:
regularFilterSettings = NotificationFilterSettings(
notFollowing: regularFilterSettings.notFollowing,
noFollower: regularFilterSettings.noFollower,
newAccount: regularFilterSettings.newAccount,
privateMentions: value)
case .adminReports:
guard let adminFilterSettings else { return }
self.adminFilterSettings = AdminNotificationFilterSettings(
filterOutReports: value,
filterOutSignups: adminFilterSettings.filterOutSignups)
case .adminSignups:
guard let adminFilterSettings else { return }
self.adminFilterSettings = AdminNotificationFilterSettings(
filterOutReports: adminFilterSettings.filterOutReports,
filterOutSignups: value)
}
}
}
protocol NotificationPolicyViewControllerDelegate: AnyObject {
func policyUpdated(_ viewController: NotificationPolicyViewController, newPolicy: Mastodon.Entity.NotificationPolicy)
func policyUpdated(
_ viewController: NotificationPolicyViewController,
newPolicy: Mastodon.Entity.NotificationPolicy)
}
class NotificationPolicyViewController: UIViewController {
@ -69,31 +161,57 @@ class NotificationPolicyViewController: UIViewController {
let tableView: UITableView
let headerBar: NotificationPolicyHeaderView
var saveItem: UIBarButtonItem?
var dataSource: UITableViewDiffableDataSource<NotificationFilterSection, NotificationFilterItem>?
let items: [NotificationFilterItem]
var dataSource:
UITableViewDiffableDataSource<
NotificationFilterSection, NotificationFilterItem
>?
let regularItems: [NotificationFilterItem]
let adminItems: [NotificationFilterItem]
var viewModel: NotificationFilterViewModel
weak var delegate: NotificationPolicyViewControllerDelegate?
init(viewModel: NotificationFilterViewModel) {
self.viewModel = viewModel
items = NotificationFilterItem.allCases
regularItems = [.notFollowing, .noFollower, .newAccount, .privateMentions]
adminItems = [.adminReports, .adminSignups]
headerBar = NotificationPolicyHeaderView()
headerBar.translatesAutoresizingMaskIntoConstraints = false
tableView = UITableView(frame: .zero, style: .insetGrouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(NotificationPolicyFilterTableViewCell.self, forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier)
tableView.register(
NotificationPolicyFilterTableViewCell.self,
forCellReuseIdentifier: NotificationPolicyFilterTableViewCell
.reuseIdentifier)
tableView.contentInset.top = -20
super.init(nibName: nil, bundle: nil)
let dataSource = UITableViewDiffableDataSource<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 {
let dataSource = UITableViewDiffableDataSource<
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 {
fatalError("No NotificationPolicyFilterTableViewCell")
}
let item = items[indexPath.row]
let item: NotificationFilterItem?
switch indexPath.section {
case 0:
item = regularItems[indexPath.row]
case 1:
item = adminItems[indexPath.row]
default:
item = nil
assertionFailure()
}
guard let item else { return nil }
cell.configure(with: item, viewModel: self.viewModel)
cell.delegate = self
@ -107,7 +225,9 @@ class NotificationPolicyViewController: UIViewController {
view.addSubview(headerBar)
view.addSubview(tableView)
view.backgroundColor = .systemGroupedBackground
headerBar.closeButton.addTarget(self, action: #selector(NotificationPolicyViewController.save(_:)), for: .touchUpInside)
headerBar.closeButton.addTarget(
self, action: #selector(NotificationPolicyViewController.save(_:)),
for: .touchUpInside)
setupConstraints()
}
@ -115,15 +235,23 @@ class NotificationPolicyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var snapshot = NSDiffableDataSourceSnapshot<NotificationFilterSection, NotificationFilterItem>()
var snapshot = NSDiffableDataSourceSnapshot<
NotificationFilterSection, NotificationFilterItem
>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
snapshot.appendItems(regularItems)
if let adminFilterSettings = viewModel.adminFilterSettings {
snapshot.appendSections([.admin])
snapshot.appendItems(adminItems)
}
dataSource?.apply(snapshot, animatingDifferences: false)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupConstraints() {
let constraints = [
@ -143,28 +271,39 @@ class NotificationPolicyViewController: UIViewController {
// MARK: - Action
@objc private func save(_ sender: UIButton) {
guard let authenticationBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { return }
guard
let authenticationBox = AuthenticationServiceProvider.shared
.currentActiveUser.value
else { return }
Task { [weak self] in
guard let self else { return }
do {
let updatedPolicy = try await APIService.shared.updateNotificationPolicy(
authenticationBox: authenticationBox,
filterNotFollowing: viewModel.notFollowing,
filterNotFollowers: viewModel.noFollower,
filterNewAccounts: viewModel.newAccount,
filterPrivateMentions: viewModel.privateMentions
).value
if let adminPreferences = viewModel.adminFilterSettings, viewModel.adminFilterSettings != viewModel.originalAdminSettings {
try await BodegaPersistence.Notifications.updatePreferences(adminPreferences, for: authenticationBox)
}
} catch {}
do {
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)
).value
delegate?.policyUpdated(self, newPolicy: updatedPolicy)
NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil)
NotificationCenter.default.post(
name: .notificationFilteringChanged, object: nil)
} catch {}
}
dismiss(animated:true)
dismiss(animated: true)
}
@objc private func cancel(_ sender: UIBarButtonItem) {
@ -175,20 +314,24 @@ class NotificationPolicyViewController: UIViewController {
//MARK: - UITableViewDelegate
extension NotificationPolicyViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
func tableView(
_ tableView: UITableView, didSelectRowAt indexPath: IndexPath
) {
tableView.deselectRow(at: indexPath, animated: true)
let filterItem = items[indexPath.row]
switch filterItem {
case .notFollowing:
viewModel.notFollowing.toggle()
case .noFollower:
viewModel.noFollower.toggle()
case .newAccount:
viewModel.newAccount.toggle()
case .privateMentions:
viewModel.privateMentions.toggle()
}
let filterItem: NotificationFilterItem? = {
switch indexPath.section {
case 0:
return regularItems[indexPath.row]
case 1:
return adminItems[indexPath.row]
default:
return nil
}
}()
guard let filterItem else { return }
let currentValue = viewModel.value(forItem: filterItem)
viewModel.setValue(!currentValue, forItem: filterItem)
if let snapshot = dataSource?.snapshot() {
dataSource?.applySnapshotUsingReloadData(snapshot)
@ -196,17 +339,13 @@ extension NotificationPolicyViewController: UITableViewDelegate {
}
}
extension NotificationPolicyViewController: NotificationPolicyFilterTableViewCellDelegate {
func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool) {
switch filterItem {
case .notFollowing:
viewModel.notFollowing = newValue
case .noFollower:
viewModel.noFollower = newValue
case .newAccount:
viewModel.newAccount = newValue
case .privateMentions:
viewModel.privateMentions = newValue
}
extension NotificationPolicyViewController:
NotificationPolicyFilterTableViewCellDelegate
{
func toggleValueChanged(
_ tableViewCell: NotificationPolicyFilterTableViewCell,
filterItem: NotificationFilterItem, newValue: Bool
) {
viewModel.setValue(newValue, forItem: filterItem)
}
}

View File

@ -119,16 +119,6 @@ extension NotificationViewController {
guard let viewModel, let policy = viewModel.notificationPolicy else { return }
Task {
let policyViewModel = await NotificationFilterViewModel(
notFollowing: policy.filterNotFollowing,
noFollower: policy.filterNotFollowers,
newAccount: policy.filterNewAccounts,
privateMentions: policy.filterPrivateMentions
)
guard let policyViewController = self.sceneCoordinator?.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return }
policyViewController.delegate = self
}
}
}

View File

@ -290,13 +290,13 @@ private extension MastodonFeedLoader {
}
}
private func _getGroupedNotifications(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> [MastodonFeedItemIdentifier] {
private func _getGroupedNotifications(withScope scope: APIService.MastodonNotificationScope? = nil, excludingAdminTypes: [Mastodon.Entity.NotificationType]?, accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> [MastodonFeedItemIdentifier] {
assert(scope != nil || accountID != nil, "need a scope or an accountID")
guard let authenticationBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { throw APIService.APIError.implicit(.authenticationMissing) }
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, excludingAdminTypes: excludingAdminTypes, authenticationBox: authenticationBox)
for account in results.accounts {
MastodonFeedItemCacheManager.shared.addToCache(account)
@ -319,13 +319,13 @@ private extension MastodonFeedLoader {
}
}
private func _getGroupedNotificationResults(withScope scope: APIService.MastodonNotificationScope? = nil, accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> Mastodon.Entity.GroupedNotificationsResults {
private func _getGroupedNotificationResults(withScope scope: APIService.MastodonNotificationScope? = nil, excludingAdminTypes: [Mastodon.Entity.NotificationType], accountID: String? = nil, olderThan maxID: String? = nil, newerThan minID: String?) async throws -> Mastodon.Entity.GroupedNotificationsResults {
assert(scope != nil || accountID != nil, "need a scope or an accountID")
guard let authenticationBox = AuthenticationServiceProvider.shared.currentActiveUser.value else { throw APIService.APIError.implicit(.authenticationMissing) }
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, authenticationBox: authenticationBox)
let results = try await APIService.shared.groupedNotifications(olderThan: maxID, newerThan: minID, fromAccount: accountID, scope: scope, excludingAdminTypes: excludingAdminTypes, authenticationBox: authenticationBox)
return results
}

View File

@ -63,6 +63,7 @@ extension APIService {
newerThan minID: Mastodon.Entity.Notification.ID?,
fromAccount accountID: String? = nil,
scope: MastodonNotificationScope?,
excludingAdminTypes: [Mastodon.Entity.NotificationType]?,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Entity.GroupedNotificationsResults {
let authorization = authenticationBox.userAuthorization
@ -73,10 +74,10 @@ extension APIService {
switch scope {
case .everything:
types = nil
excludedTypes = [.adminReport, .adminSignUp]
excludedTypes = excludingAdminTypes
case .mentions:
types = [.mention]
excludedTypes = [.follow, .followRequest, .reblog, .favourite, .poll]
excludedTypes = [.follow, .followRequest, .reblog, .favourite, .poll,.adminReport, .adminSignUp]
case nil:
types = nil
excludedTypes = nil

View File

@ -943,6 +943,20 @@ public enum L10n {
public static let learnMoreAboutServerBlocks = L10n.tr("Localizable", "Scene.Notification.LearnMoreAboutServerBlocks", fallback: "Learn more about server blocks")
/// View report
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")
/// 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")
/// Account signups
public static let title = L10n.tr("Localizable", "Scene.Notification.AdminFilter.Signups.Title", fallback: "Account signups")
}
}
public enum FilteredNotification {
/// Accept
public static let accept = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Accept", fallback: "Accept")

View File

@ -392,6 +392,10 @@ Please retry in a few minutes.";
"Scene.Notification.Policy.PrivateMentions.Subtitle" = "Filtered unless its in reply to your own mention or if you follow the sender";
"Scene.Notification.Policy.PrivateMentions.Title" = "Unsolicited private mentions";
"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.Signups.Title" = "Account signups";
"Scene.Notification.AdminFilter.Signups.Subtitle" = "Hide 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.";