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:
parent
d345a6872a
commit
8153c16932
@ -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 it’s 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": {
|
||||
|
@ -827,6 +827,16 @@
|
||||
"title": "Unsolicited private mentions",
|
||||
"subtitle": "Filtered unless it’s 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": {
|
||||
|
@ -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 */,
|
||||
|
24
Mastodon/BodegaPersistence.swift
Normal file
24
Mastodon/BodegaPersistence.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -392,6 +392,10 @@ Please retry in a few minutes.";
|
||||
"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.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.";
|
||||
|
Loading…
x
Reference in New Issue
Block a user