From cf39ad6d68b0177245c486f1e8b187c4f726e211 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 24 Jun 2024 11:28:16 +0200 Subject: [PATCH 01/48] Add notification-policy-download (IOS-241) --- .../Service/API/APIService+Notification.swift | 11 +++++++ .../API/Mastodon+API+Notifications.swift | 25 +++++++++++++++ .../Mastodon+Entity+NotificationPolicy.swift | 31 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index ff26e69b5c..b66e8fe23c 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -107,3 +107,14 @@ extension APIService { } } + +extension APIService { + public func notificationPolicy(authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization).singleOutput() + + return response + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index f70caaa1dc..f10740b2a5 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -134,3 +134,28 @@ extension Mastodon.API.Notifications { } } } + +//MARK: - Notification Policy + +extension Mastodon.API.Notifications { + internal static func notificationPolicyEndpointURL(domain: String) -> URL { + notificationsEndpointURL(domain: domain).appendingPathComponent("policy") + } + + public static func getNotificationPolicy( + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: notificationPolicyEndpointURL(domain: domain), + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift new file mode 100644 index 0000000000..4f487eb2c1 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift @@ -0,0 +1,31 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import Foundation + +extension Mastodon.Entity { + public struct NotificationPolicy: Codable { + let filterNotFollowing: Bool + let filterNotFollowers: Bool + let filterNewAccounts: Bool + let filterPrivateMentions: Bool + 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 summary + } + + public struct Summary: Codable { + let pendingRequestsCount: Int + let pendingNotificationsCount: Int + + enum CodingKeys: String, CodingKey { + case pendingRequestsCount = "pending_requests_count" + case pendingNotificationsCount = "pending_notifications_count" + } + } + } +} From 311886198454f605efaa8e14e3df721d80bd1363 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 25 Jun 2024 10:05:32 +0200 Subject: [PATCH 02/48] Add a filtering-button that is only visible if there's a policy (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 12 +++++++++ .../NotificationPolicyViewController.swift | 13 ++++++++++ .../NotificationViewController.swift | 25 +++++++++++++------ .../Notification/NotificationViewModel.swift | 11 ++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ab6ac29406..3d6f6970f7 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -137,6 +137,7 @@ D8099078294BC8A30050219F /* PrivacyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8099077294BC8A30050219F /* PrivacyTableViewController.swift */; }; D809907A294BC9390050219F /* PrivacyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8099079294BC9390050219F /* PrivacyTableViewCell.swift */; }; D809907C294D25510050219F /* PrivacyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D809907B294D25510050219F /* PrivacyViewModel.swift */; }; + D80EC2632C2978EE009724A5 /* NotificationPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */; }; D80F627C2B5C32C500877059 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80F627A2B5C32C500877059 /* NotificationView.swift */; }; D81439862AD415DE0071A88F /* AboutInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81439852AD415DE0071A88F /* AboutInstance.swift */; }; D81439882AD450A40071A88F /* AboutInstanceTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81439872AD450A40071A88F /* AboutInstanceTableViewDataSource.swift */; }; @@ -761,6 +762,7 @@ D8099077294BC8A30050219F /* PrivacyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyTableViewController.swift; sourceTree = ""; }; D8099079294BC9390050219F /* PrivacyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyTableViewCell.swift; sourceTree = ""; }; D809907B294D25510050219F /* PrivacyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyViewModel.swift; sourceTree = ""; }; + D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyViewController.swift; sourceTree = ""; }; D80F627A2B5C32C500877059 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; D81439852AD415DE0071A88F /* AboutInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstance.swift; sourceTree = ""; }; D81439872AD450A40071A88F /* AboutInstanceTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceTableViewDataSource.swift; sourceTree = ""; }; @@ -1758,6 +1760,14 @@ path = Privacy; sourceTree = ""; }; + D80EC2602C2978CB009724A5 /* Notification Filtering */ = { + isa = PBXGroup; + children = ( + D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, + ); + path = "Notification Filtering"; + sourceTree = ""; + }; D80F627E2B5C32E400877059 /* NotificationView */ = { isa = PBXGroup; children = ( @@ -2667,6 +2677,7 @@ DB9D6BFD25E4F57B0051B173 /* Notification */ = { isa = PBXGroup; children = ( + D80EC2602C2978CB009724A5 /* Notification Filtering */, DB63F765279A5E5600455B82 /* NotificationTimeline */, 2D35237F26256F470031AF25 /* Cell */, D80F627E2B5C32E400877059 /* NotificationView */, @@ -3808,6 +3819,7 @@ DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */, D8318A802A4466D300C0FB73 /* SettingsCoordinator.swift in Sources */, DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */, + D80EC2632C2978EE009724A5 /* NotificationPolicyViewController.swift in Sources */, DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */, DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */, DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */, diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift new file mode 100644 index 0000000000..0aff29b496 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -0,0 +1,13 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit + +class NotificationPolicyViewController: UIViewController { + init() { + super.init(nibName: nil, bundle: nil) + + view.backgroundColor = .systemGreen + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } +} diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index df0b7d0fa5..6376fc5a19 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -86,14 +86,11 @@ extension NotificationViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) -// aspectViewWillAppear(animated) - - // fetch latest notification when scroll position is within half screen height to prevent list reload -// if tableView.contentOffset.y < view.frame.height * 0.5 { -// viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self) -// } + // https://github.com/mastodon/documentation/pull/1447#issuecomment-2149225659 + if let viewModel, viewModel.notificationPolicy != nil { + navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease"), style: .plain, target: self, action: #selector(NotificationViewController.showNotificationPolicySettings(_:))) + } - // needs trigger manually after onboarding dismiss setNeedsStatusBarAppearanceUpdate() } @@ -117,6 +114,20 @@ extension NotificationViewController { // aspectViewDidDisappear(animated) } + + //MARK: - Actions + + @objc private func showNotificationPolicySettings(_ sender: Any) { + //TODO: Move to SceneCoordinator + let notificationPolicyViewController = NotificationPolicyViewController() + notificationPolicyViewController.modalPresentationStyle = .formSheet + + if let sheet = notificationPolicyViewController.sheetPresentationController { + sheet.detents = [.medium(), .large()] + } + + present(UINavigationController(rootViewController: notificationPolicyViewController), animated: true) + } } extension NotificationViewController { diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index b3a3316fd4..9626bcaa66 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -11,6 +11,7 @@ import Pageboy import MastodonAsset import MastodonCore import MastodonLocalization +import MastodonSDK final class NotificationViewModel { @@ -19,6 +20,7 @@ final class NotificationViewModel { // input let context: AppContext let authContext: AuthContext + var notificationPolicy: Mastodon.Entity.NotificationPolicy? let viewDidLoad = PassthroughSubject() // output @@ -50,7 +52,16 @@ final class NotificationViewModel { init(context: AppContext, authContext: AuthContext) { self.context = context self.authContext = authContext + // end init + Task { + do { + let policy = try await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox) + self.notificationPolicy = policy.value + } catch { + // we won't show the filtering-options. + } + } } } From f297cbabc5c23bcfe362e23b3d52d3b9ae4e54ae Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 25 Jun 2024 10:08:52 +0200 Subject: [PATCH 03/48] Fix detens (IOS-241) --- Mastodon/Scene/Notification/NotificationViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 6376fc5a19..44c9d29d8d 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -121,12 +121,13 @@ extension NotificationViewController { //TODO: Move to SceneCoordinator let notificationPolicyViewController = NotificationPolicyViewController() notificationPolicyViewController.modalPresentationStyle = .formSheet + let navigationController = UINavigationController(rootViewController: notificationPolicyViewController) - if let sheet = notificationPolicyViewController.sheetPresentationController { + if let sheet = navigationController.sheetPresentationController { sheet.detents = [.medium(), .large()] } - present(UINavigationController(rootViewController: notificationPolicyViewController), animated: true) + present(navigationController, animated: true) } } From 484334803412d6cfe8c267a80b8488cbd8a75986 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 25 Jun 2024 12:09:24 +0200 Subject: [PATCH 04/48] Add and show filter-banner in "everything"-notifications (if there are any) (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 4 ++ .../DataSourceFacade+SearchHistory.swift | 44 +++++++------ ...er+NotificationTableViewCellDelegate.swift | 6 +- ...Provider+StatusTableViewCellDelegate.swift | 4 +- ...taSourceProvider+UITableViewDelegate.swift | 61 ++++++++++--------- .../Provider/DataSourceProvider.swift | 1 + ...ficationFilteringBannerTableViewCell.swift | 22 +++++++ .../Scene/Notification/NotificationItem.swift | 1 + .../Notification/NotificationSection.swift | 7 +++ ...ineViewController+DataSourceProvider.swift | 4 +- .../NotificationTimelineViewController.swift | 18 ++++-- ...tificationTimelineViewModel+Diffable.swift | 5 +- .../NotificationTimelineViewModel.swift | 5 +- .../NotificationViewController.swift | 19 +++--- ...ultViewController+DataSourceProvider.swift | 2 +- .../Mastodon+Entity+NotificationPolicy.swift | 18 +++--- 16 files changed, 137 insertions(+), 84 deletions(-) create mode 100644 Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 3d6f6970f7..0743f5b723 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */; }; D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A892A4468DC00C0FB73 /* AboutViewController.swift */; }; D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; }; + D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; }; D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; @@ -791,6 +792,7 @@ D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = ""; }; D8318A892A4468DC00C0FB73 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = ""; tabWidth = 4; }; + D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = ""; }; D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; @@ -1512,6 +1514,7 @@ DB63F76E279A7D1100455B82 /* NotificationTableViewCell.swift */, DB63F774279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift */, D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */, + D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */, ); path = Cell; sourceTree = ""; @@ -3780,6 +3783,7 @@ 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, + D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */, DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */, D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */, D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift index 76b19fa106..63ccabbf9e 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift @@ -17,32 +17,30 @@ extension DataSourceFacade { item: DataSourceItem ) async { switch item { - case .account(account: let account, relationship: _): - let now = Date() - let userID = provider.authContext.mastodonAuthenticationBox.userID - let searchEntry = Persistence.SearchHistory.Item( - updatedAt: now, - userID: userID, - account: account, - hashtag: nil - ) + case .account(account: let account, relationship: _): + let now = Date() + let userID = provider.authContext.mastodonAuthenticationBox.userID + let searchEntry = Persistence.SearchHistory.Item( + updatedAt: now, + userID: userID, + account: account, + hashtag: nil + ) - try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox) - case .hashtag(let tag): + try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox) + case .hashtag(let tag): - let now = Date() - let userID = provider.authContext.mastodonAuthenticationBox.userID - let searchEntry = Persistence.SearchHistory.Item( - updatedAt: now, - userID: userID, - account: nil, - hashtag: tag - ) + let now = Date() + let userID = provider.authContext.mastodonAuthenticationBox.userID + let searchEntry = Persistence.SearchHistory.Item( + updatedAt: now, + userID: userID, + account: nil, + hashtag: tag + ) - try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox) - case .status: - break - case .notification: + try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox) + case .status, .notification, .notificationBanner(_): break } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index aa71991d3b..5d23083b85 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -514,10 +514,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut ) case .account(let account, _): await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) - case .notification: - assertionFailure("TODO") - case .hashtag(_): - assertionFailure("TODO") + case .notification, .hashtag(_), .notificationBanner(_): + print("TODO") } } // end Task } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index 4381498907..24ad78b286 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -618,9 +618,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte provider: self, account: account ) - case .notification: - assertionFailure("TODO") - case .hashtag(_): + case .notification, .hashtag(_), .notificationBanner(_): assertionFailure("TODO") } } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift index d5e654dcad..712a169703 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift @@ -22,42 +22,45 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid return } switch item { - case .account(let account, relationship: _): - await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) - case .status(let status): + case .account(let account, relationship: _): + await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) + case .status(let status): + await DataSourceFacade.coordinateToStatusThreadScene( + provider: self, + target: .status, // remove reblog wrapper + status: status + ) + case .hashtag(let tag): + await DataSourceFacade.coordinateToHashtagScene( + provider: self, + tag: tag + ) + case .notification(let notification): + let _status: MastodonStatus? = notification.status + if let status = _status { await DataSourceFacade.coordinateToStatusThreadScene( provider: self, target: .status, // remove reblog wrapper status: status ) - case .hashtag(let tag): - await DataSourceFacade.coordinateToHashtagScene( - provider: self, - tag: tag + } else if let accountWarning = notification.entity.accountWarning { + let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id) + _ = coordinator.present( + scene: .safari(url: url), + from: self, + transition: .safariPresent(animated: true, completion: nil) ) - case .notification(let notification): - let _status: MastodonStatus? = notification.status - if let status = _status { - await DataSourceFacade.coordinateToStatusThreadScene( - provider: self, - target: .status, // remove reblog wrapper - status: status - ) - } else if let accountWarning = notification.entity.accountWarning { - let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id) - _ = coordinator.present( - scene: .safari(url: url), - from: self, - transition: .safariPresent(animated: true, completion: nil) - ) - } else { - await DataSourceFacade.coordinateToProfileScene( - provider: self, - account: notification.entity.account - ) - } // end Task - } // end func + } else { + await DataSourceFacade.coordinateToProfileScene( + provider: self, + account: notification.entity.account + ) + } + case .notificationBanner(let policy): + //TODO: Coordinate to pending notification-screen + break + } } } } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider.swift b/Mastodon/Protocol/Provider/DataSourceProvider.swift index fe886800e1..b6e1134fb3 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider.swift @@ -14,6 +14,7 @@ enum DataSourceItem: Hashable { case status(record: MastodonStatus) case hashtag(tag: Mastodon.Entity.Tag) case notification(record: MastodonNotification) + case notificationBanner(policy: Mastodon.Entity.NotificationPolicy) case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) } diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift new file mode 100644 index 0000000000..409673e2eb --- /dev/null +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -0,0 +1,22 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonSDK + +class NotificationFilteringBannerTableViewCell: UITableViewCell { + static let reuseIdentifier = "NotificationFilteringBannerTableViewCell" + + //TODO: Add separator + + func configure(with policy: Mastodon.Entity.NotificationPolicy) { + var configuration = defaultContentConfiguration() + + //TODO: Add localization + configuration.text = "Filtered notifications" + configuration.secondaryText = "\(policy.summary.pendingRequestsCount) people you may know" + configuration.image = UIImage(systemName: "archivebox") + + self.contentConfiguration = configuration + + } +} diff --git a/Mastodon/Scene/Notification/NotificationItem.swift b/Mastodon/Scene/Notification/NotificationItem.swift index d5727e813e..72ca3d8454 100644 --- a/Mastodon/Scene/Notification/NotificationItem.swift +++ b/Mastodon/Scene/Notification/NotificationItem.swift @@ -10,6 +10,7 @@ import Foundation import MastodonSDK enum NotificationItem: Hashable { + case filteredNotifications(policy: Mastodon.Entity.NotificationPolicy) case feed(record: MastodonFeed) case feedLoader(record: MastodonFeed) case bottomLoader diff --git a/Mastodon/Scene/Notification/NotificationSection.swift b/Mastodon/Scene/Notification/NotificationSection.swift index ac80faf974..cb630b904e 100644 --- a/Mastodon/Scene/Notification/NotificationSection.swift +++ b/Mastodon/Scene/Notification/NotificationSection.swift @@ -39,6 +39,7 @@ extension NotificationSection { tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self)) tableView.register(AccountWarningNotificationCell.self, forCellReuseIdentifier: AccountWarningNotificationCell.reuseIdentifier) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) + tableView.register(NotificationFilteringBannerTableViewCell.self, forCellReuseIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier) return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in switch item { @@ -67,6 +68,12 @@ extension NotificationSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell cell.activityIndicatorView.startAnimating() return cell + + case .filteredNotifications(let policy): + let cell = tableView.dequeueReusableCell(withIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier, for: indexPath) as! NotificationFilteringBannerTableViewCell + cell.configure(with: policy) + + return cell } } } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift index e4bcc8a125..04a8820a4e 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController+DataSourceProvider.swift @@ -33,7 +33,9 @@ extension NotificationTimelineViewController: DataSourceProvider { } }() return item - default: + case .filteredNotifications(let policy): + return DataSourceItem.notificationBanner(policy: policy) + case .bottomLoader, .feedLoader(_): return nil } } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index cd8a3158e9..bdb00cb6db 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -13,16 +13,16 @@ import MastodonLocalization final class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { - weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } - weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + weak var context: AppContext! + weak var coordinator: SceneCoordinator! let mediaPreviewTransitionController = MediaPreviewTransitionController() var disposeBag = Set() var observations = Set() - var viewModel: NotificationTimelineViewModel! - + let viewModel: NotificationTimelineViewModel + private(set) lazy var refreshControl: RefreshControl = { let refreshControl = RefreshControl() refreshControl.addTarget(self, action: #selector(NotificationTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged) @@ -38,6 +38,16 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc }() let cellFrameCache = NSCache() + + init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator) { + self.viewModel = viewModel + self.context = context + self.coordinator = coordinator + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension NotificationTimelineViewController { diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift index 9c5bf2949c..6070f48d41 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift @@ -33,7 +33,7 @@ extension NotificationTimelineViewModel { dataController.$records .receive(on: DispatchQueue.main) .sink { [weak self] records in - guard let self = self else { return } + guard let self else { return } guard let diffableDataSource = self.diffableDataSource else { return } Task { @@ -44,6 +44,9 @@ extension NotificationTimelineViewModel { } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) + if self.scope == .everything, let notificationPolicy = self.notificationPolicy, notificationPolicy.summary.pendingRequestsCount > 0 { + snapshot.appendItems([.filteredNotifications(policy: notificationPolicy)]) + } snapshot.appendItems(newItems.removingDuplicates(), toSection: .main) return snapshot }() diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 0df34434a4..6282c37739 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -20,6 +20,7 @@ final class NotificationTimelineViewModel { let context: AppContext let authContext: AuthContext let scope: Scope + let notificationPolicy: Mastodon.Entity.NotificationPolicy? let dataController: FeedDataController @Published var isLoadingLatest = false @Published var lastAutomaticFetchTimestamp: Date? @@ -46,12 +47,14 @@ final class NotificationTimelineViewModel { init( context: AppContext, authContext: AuthContext, - scope: Scope + scope: Scope, + notificationPolicy: Mastodon.Entity.NotificationPolicy? ) { self.context = context self.authContext = authContext self.scope = scope self.dataController = FeedDataController(context: context, authContext: authContext) + self.notificationPolicy = notificationPolicy switch scope { case .everything: diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 44c9d29d8d..bcfeaf01e8 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -146,17 +146,20 @@ extension NotificationViewController { pageSegmentedControl.selectedSegmentIndex = 0 } } - + private func createViewController(for scope: NotificationTimelineViewModel.Scope) -> UIViewController { - guard let authContext = viewModel?.authContext else { return UITableViewController() } - let viewController = NotificationTimelineViewController() - viewController.context = context - viewController.coordinator = coordinator - viewController.viewModel = NotificationTimelineViewModel( + guard let viewModel else { return UITableViewController() } + + let viewController = NotificationTimelineViewController( + viewModel: NotificationTimelineViewModel( + context: context, + authContext: viewModel.authContext, + scope: scope, notificationPolicy: viewModel.notificationPolicy + ), context: context, - authContext: authContext, - scope: scope + coordinator: coordinator ) + return viewController } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift index ff1eab32ba..4891b175b9 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift @@ -70,7 +70,7 @@ extension SearchResultViewController { provider: self, tag: tag ) - case .notification: + case .notification, .notificationBanner(_): assertionFailure() } // end switch diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift index 4f487eb2c1..68755e9b60 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationPolicy.swift @@ -3,12 +3,12 @@ import Foundation extension Mastodon.Entity { - public struct NotificationPolicy: Codable { - let filterNotFollowing: Bool - let filterNotFollowers: Bool - let filterNewAccounts: Bool - let filterPrivateMentions: Bool - let summary: Summary + public struct NotificationPolicy: Codable, Hashable { + public let filterNotFollowing: Bool + public let filterNotFollowers: Bool + public let filterNewAccounts: Bool + public let filterPrivateMentions: Bool + public let summary: Summary enum CodingKeys: String, CodingKey { case filterNotFollowing = "filter_not_following" @@ -18,9 +18,9 @@ extension Mastodon.Entity { case summary } - public struct Summary: Codable { - let pendingRequestsCount: Int - let pendingNotificationsCount: Int + public struct Summary: Codable, Hashable { + public let pendingRequestsCount: Int + public let pendingNotificationsCount: Int enum CodingKeys: String, CodingKey { case pendingRequestsCount = "pending_requests_count" From 151880f661305465ca758f6f1a09c8bf91603e76 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 8 Jul 2024 11:38:57 +0200 Subject: [PATCH 05/48] Add basic UI filter notification-filtering-setting (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 4 + ...otificationPolicyFilterTableViewCell.swift | 49 +++++++ .../NotificationPolicyViewController.swift | 125 +++++++++++++++++- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 0743f5b723..07c93f2d3e 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; }; D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; }; D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; }; + D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -794,6 +795,7 @@ D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = ""; tabWidth = 4; }; D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = ""; }; D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; }; + D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1767,6 +1769,7 @@ isa = PBXGroup; children = ( D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, + D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */, ); path = "Notification Filtering"; sourceTree = ""; @@ -3559,6 +3562,7 @@ DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */, 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */, DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */, + D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */, DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */, DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */, D8FAAE412AD0475900DC1832 /* AboutInstanceTableViewHeader.swift in Sources */, diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift new file mode 100644 index 0000000000..e2e59d259d --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift @@ -0,0 +1,49 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit + +protocol NotificationPolicyFilterTableViewCellDelegate: AnyObject { + func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool) +} + +class NotificationPolicyFilterTableViewCell: ToggleTableViewCell { + override class var reuseIdentifier: String { + return "NotificationPolicyFilterTableViewCell" + } + + var filterItem: NotificationFilterItem? + weak var delegate: NotificationPolicyFilterTableViewCellDelegate? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + toggle.addTarget(self, action: #selector(NotificationPolicyFilterTableViewCell.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 + 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 + } + + toggle.isOn = toggleIsOn + } + + @objc func toggleValueChanged(_ sender: UISwitch) { + guard let filterItem, let delegate else { return } + + delegate.toggleValueChanged(self, filterItem: filterItem, newValue: sender.isOn) + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index 0aff29b496..9dd3dfe6ea 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -1,13 +1,134 @@ // Copyright © 2024 Mastodon gGmbH. All rights reserved. import UIKit +import MastodonLocalization + +enum NotificationFilterSection: Hashable { + case main +} + +enum NotificationFilterItem: Hashable, CaseIterable { + case notFollowing + case noFollower + case newAccount + case privateMentions + + var title: String { + switch self { + case .notFollowing: + return "People you don't follow" + case .noFollower: + return "People not following you" + case .newAccount: + return "New accounts" + case .privateMentions: + return "Unsolicited private mentions" + } + } +} + +struct NotificationFilterViewModel { + var notFollowing: Bool + var noFollower: Bool + var newAccount: Bool + var privateMentions: Bool + + init(notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) { + self.notFollowing = notFollowing + self.noFollower = noFollower + self.newAccount = newAccount + self.privateMentions = privateMentions + } +} class NotificationPolicyViewController: UIViewController { + + //TODO: DataSource, Source, Items + let tableView: UITableView + var dataSource: UITableViewDiffableDataSource? + let items: [NotificationFilterItem] + let viewModel: NotificationFilterViewModel + + init() { + //TODO: Dependency Inject Policy ViewModel + viewModel = NotificationFilterViewModel(notFollowing: false, noFollower: false, newAccount: false, privateMentions: false) + items = NotificationFilterItem.allCases + + tableView = UITableView(frame: .zero, style: .insetGrouped) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.register(NotificationPolicyFilterTableViewCell.self, forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier) + super.init(nibName: nil, bundle: nil) - view.backgroundColor = .systemGreen + let dataSource = UITableViewDiffableDataSource(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") + } + + //TODO: Configuration + + let item = items[indexPath.row] + cell.configure(with: item, viewModel: self.viewModel) + cell.delegate = self + + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self + + self.dataSource = dataSource + view.addSubview(tableView) + view.backgroundColor = .systemGroupedBackground + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) + navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .done, target: self, action: #selector(NotificationPolicyViewController.cancel(_:))) + + setupConstraints() + } + + override func viewDidLoad() { + super.viewDidLoad() + + var snapshot = NSDiffableDataSourceSnapshot() + + snapshot.appendSections([.main]) + snapshot.appendItems(items) + + dataSource?.apply(snapshot, animatingDifferences: false) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: tableView.bottomAnchor), + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Action + + @objc private func save(_ sender: UIBarButtonItem) { + //TODO: Save + } + + @objc private func cancel(_ sender: UIBarButtonItem) { + dismiss(animated: true) + } +} + +extension NotificationPolicyViewController: UITableViewDelegate { + +} + +extension NotificationPolicyViewController: NotificationPolicyFilterTableViewCellDelegate { + func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool) { + //TODO: Update ViewModel + } } From 0b749816dd16cfaffcb83184d9bf701f5292f80e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 8 Jul 2024 12:09:01 +0200 Subject: [PATCH 06/48] Show subtitle (IOS-241) --- ...otificationPolicyFilterTableViewCell.swift | 1 + .../NotificationPolicyViewController.swift | 13 ++++++++++ .../GeneralSettingToggleTableViewCell.swift | 1 + ...tificationSettingTableViewToggleCell.swift | 1 + .../Settings/Shared/ToggleTableViewCell.swift | 26 +++++++++++++------ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift index e2e59d259d..8359d95e90 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift @@ -24,6 +24,7 @@ class NotificationPolicyFilterTableViewCell: ToggleTableViewCell { public func configure(with filterItem: NotificationFilterItem, viewModel: NotificationFilterViewModel) { label.text = filterItem.title + subtitleLabel.text = filterItem.subtitle self.filterItem = filterItem let toggleIsOn: Bool diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index 9dd3dfe6ea..50645897b2 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -25,6 +25,19 @@ enum NotificationFilterItem: Hashable, CaseIterable { return "Unsolicited private mentions" } } + + var subtitle: String { + switch self { + case .notFollowing: + return "Until you manually approve them" + case .noFollower: + return "Including people who have been following you fewer than 3 days" + case .newAccount: + return "Created within the past 30 days" + case .privateMentions: + return "Filtered unless it’s in reply to your own mention or if you follow the sender" + } + } } struct NotificationFilterViewModel { diff --git a/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift b/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift index 5775fdcf5f..ab74574215 100644 --- a/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/General Settings/GeneralSettingToggleTableViewCell.swift @@ -17,6 +17,7 @@ class GeneralSettingToggleTableViewCell: ToggleTableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) + subtitleLabel.isHidden = true toggle.addTarget(self, action: #selector(GeneralSettingToggleTableViewCell.toggleValueChanged(_:)), for: .valueChanged) } diff --git a/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift b/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift index 6a8dfb0631..5bb8cff1f2 100644 --- a/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift +++ b/Mastodon/Scene/Settings/Notification Settings/Cells/NotificationSettingTableViewToggleCell.swift @@ -18,6 +18,7 @@ class NotificationSettingTableViewToggleCell: ToggleTableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) + subtitleLabel.isHidden = true toggle.addTarget(self, action: #selector(NotificationSettingTableViewToggleCell.toggleValueChanged(_:)), for: .valueChanged) } diff --git a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift index 11454d9e63..0b49875eac 100644 --- a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift @@ -9,22 +9,32 @@ class ToggleTableViewCell: UITableViewCell { } let label: UILabel + let subtitleLabel: UILabel + private let labelStackView: UIStackView let toggle: UISwitch override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false 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 + toggle = UISwitch() toggle.translatesAutoresizingMaskIntoConstraints = false toggle.onTintColor = Asset.Colors.Brand.blurple.color super.init(style: style, reuseIdentifier: reuseIdentifier) - contentView.addSubview(label) + contentView.addSubview(labelStackView) contentView.addSubview(toggle) setupConstraints() } @@ -33,11 +43,11 @@ class ToggleTableViewCell: UITableViewCell { private func setupConstraints() { let constraints = [ - label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11), - label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - contentView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 11), - - toggle.leadingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 16), + labelStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11), + labelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + contentView.bottomAnchor.constraint(equalTo: labelStackView.bottomAnchor, constant: 11), + + toggle.leadingAnchor.constraint(greaterThanOrEqualTo: labelStackView.trailingAnchor, constant: 16), toggle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), contentView.trailingAnchor.constraint(equalTo: toggle.trailingAnchor, constant: 16) From 94ad86cd666f5a978edb04f8be0115e1b21e439b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 8 Jul 2024 12:13:29 +0200 Subject: [PATCH 07/48] Dependencyinject ViewModel based on downloaded policy-data (IOS-241) --- .../NotificationPolicyViewController.swift | 25 +++++++++++++------ .../NotificationViewController.swift | 5 +++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index 50645897b2..d9e2b87617 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -60,12 +60,10 @@ class NotificationPolicyViewController: UIViewController { let tableView: UITableView var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] - let viewModel: NotificationFilterViewModel + var viewModel: NotificationFilterViewModel - - init() { - //TODO: Dependency Inject Policy ViewModel - viewModel = NotificationFilterViewModel(notFollowing: false, noFollower: false, newAccount: false, privateMentions: false) + init(viewModel: NotificationFilterViewModel) { + self.viewModel = viewModel items = NotificationFilterItem.allCases tableView = UITableView(frame: .zero, style: .insetGrouped) @@ -128,7 +126,7 @@ class NotificationPolicyViewController: UIViewController { // MARK: - Action @objc private func save(_ sender: UIBarButtonItem) { - //TODO: Save + //TODO: Save aka PATH viewModel to API and dismiss } @objc private func cancel(_ sender: UIBarButtonItem) { @@ -137,11 +135,22 @@ class NotificationPolicyViewController: UIViewController { } extension NotificationPolicyViewController: UITableViewDelegate { - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } } extension NotificationPolicyViewController: NotificationPolicyFilterTableViewCellDelegate { func toggleValueChanged(_ tableViewCell: NotificationPolicyFilterTableViewCell, filterItem: NotificationFilterItem, newValue: Bool) { - //TODO: Update ViewModel + switch filterItem { + case .notFollowing: + viewModel.notFollowing = newValue + case .noFollower: + viewModel.noFollower = newValue + case .newAccount: + viewModel.newAccount = newValue + case .privateMentions: + viewModel.privateMentions = newValue + } } } diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index bcfeaf01e8..12dd976fc0 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -118,8 +118,11 @@ extension NotificationViewController { //MARK: - Actions @objc private func showNotificationPolicySettings(_ sender: Any) { + guard let policy = viewModel?.notificationPolicy else { return } + + let policyViewModel = NotificationFilterViewModel(notFollowing: policy.filterNotFollowing, noFollower: policy.filterNotFollowers, newAccount: policy.filterNewAccounts, privateMentions: policy.filterPrivateMentions) //TODO: Move to SceneCoordinator - let notificationPolicyViewController = NotificationPolicyViewController() + let notificationPolicyViewController = NotificationPolicyViewController(viewModel: policyViewModel) notificationPolicyViewController.modalPresentationStyle = .formSheet let navigationController = UINavigationController(rootViewController: notificationPolicyViewController) From 363a521e6841325022b756933a60b9b9b0d15f4b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 10:46:19 +0200 Subject: [PATCH 08/48] UI/UX-improvements (IOS-241) --- ...otificationPolicyFilterTableViewCell.swift | 3 +++ .../NotificationPolicyViewController.swift | 25 ++++++++++++++++--- .../Settings/Shared/ToggleTableViewCell.swift | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift index 8359d95e90..f07f1b0668 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift @@ -17,6 +17,9 @@ class NotificationPolicyFilterTableViewCell: ToggleTableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) + label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) + subtitleLabel.textColor = .secondaryLabel + toggle.addTarget(self, action: #selector(NotificationPolicyFilterTableViewCell.toggleValueChanged(_:)), for: .valueChanged) } diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index d9e2b87617..e1f09c3c57 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -2,6 +2,7 @@ import UIKit import MastodonLocalization +import MastodonAsset enum NotificationFilterSection: Hashable { case main @@ -14,6 +15,7 @@ enum NotificationFilterItem: Hashable, CaseIterable { case privateMentions var title: String { + // TODO: Localization switch self { case .notFollowing: return "People you don't follow" @@ -27,6 +29,7 @@ enum NotificationFilterItem: Hashable, CaseIterable { } var subtitle: String { + // TODO: Localization switch self { case .notFollowing: return "Until you manually approve them" @@ -56,7 +59,6 @@ struct NotificationFilterViewModel { class NotificationPolicyViewController: UIViewController { - //TODO: DataSource, Source, Items let tableView: UITableView var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] @@ -77,8 +79,6 @@ class NotificationPolicyViewController: UIViewController { fatalError("No NotificationPolicyFilterTableViewCell") } - //TODO: Configuration - let item = items[indexPath.row] cell.configure(with: item, viewModel: self.viewModel) cell.delegate = self @@ -86,6 +86,9 @@ class NotificationPolicyViewController: UIViewController { return cell } + // TODO: Localization + title = "Filter Notifications from" + tableView.dataSource = dataSource tableView.delegate = self @@ -137,6 +140,22 @@ class NotificationPolicyViewController: UIViewController { extension NotificationPolicyViewController: UITableViewDelegate { 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() + } + + if let snapshot = dataSource?.snapshot() { + dataSource?.applySnapshotUsingReloadData(snapshot) + } } } diff --git a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift index 0b49875eac..db47a7f75f 100644 --- a/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift @@ -27,6 +27,7 @@ class ToggleTableViewCell: UITableViewCell { labelStackView.translatesAutoresizingMaskIntoConstraints = false labelStackView.alignment = .leading labelStackView.axis = .vertical + labelStackView.spacing = 4 toggle = UISwitch() toggle.translatesAutoresizingMaskIntoConstraints = false From 847cadb18230e25ab717d2544278ce86fc580b32 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 15:06:05 +0200 Subject: [PATCH 09/48] Upload updated policy (IOS-241) --- .../NotificationPolicyViewController.swift | 44 ++++++++++++++-- .../NotificationViewController.swift | 10 +++- .../Service/API/APIService+Notification.swift | 23 ++++++++- .../API/Mastodon+API+Notifications.swift | 51 ++++++++++++++++--- .../Sources/MastodonSDK/Query/Query.swift | 5 ++ 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index e1f09c3c57..781aba6cd3 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -3,6 +3,7 @@ import UIKit import MastodonLocalization import MastodonAsset +import MastodonCore enum NotificationFilterSection: Hashable { case main @@ -49,7 +50,10 @@ struct NotificationFilterViewModel { var newAccount: Bool var privateMentions: Bool - init(notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) { + let appContext: AppContext + + init(appContext: AppContext, notFollowing: Bool, noFollower: Bool, newAccount: Bool, privateMentions: Bool) { + self.appContext = appContext self.notFollowing = notFollowing self.noFollower = noFollower self.newAccount = newAccount @@ -60,6 +64,7 @@ struct NotificationFilterViewModel { class NotificationPolicyViewController: UIViewController { let tableView: UITableView + var saveItem: UIBarButtonItem? var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] var viewModel: NotificationFilterViewModel @@ -96,7 +101,8 @@ class NotificationPolicyViewController: UIViewController { view.addSubview(tableView) view.backgroundColor = .systemGroupedBackground - navigationItem.rightBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) + saveItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) + navigationItem.rightBarButtonItem = saveItem navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .done, target: self, action: #selector(NotificationPolicyViewController.cancel(_:))) setupConstraints() @@ -129,7 +135,39 @@ class NotificationPolicyViewController: UIViewController { // MARK: - Action @objc private func save(_ sender: UIBarButtonItem) { - //TODO: Save aka PATH viewModel to API and dismiss + guard let authenticationBox = viewModel.appContext.authenticationService.mastodonAuthenticationBoxes.first else { return } + + navigationItem.leftBarButtonItem?.isEnabled = false + + let activityIndicator = UIActivityIndicatorView(style: .medium) + + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activityIndicator) + navigationItem.rightBarButtonItem?.isEnabled = false + + activityIndicator.startAnimating() + + Task { [weak self] in + guard let self else { return } + + do { + let result = try await viewModel.appContext.apiService.updateNotificationPolicy( + authenticationBox: authenticationBox, + filterNotFollowing: viewModel.notFollowing, + filterNotFollowers: viewModel.noFollower, + filterNewAccounts: viewModel.newAccount, + filterPrivateMentions: viewModel.privateMentions + ) + + await MainActor.run { + self.dismiss(animated:true) + } + } catch { + navigationItem.rightBarButtonItem = saveItem + navigationItem.rightBarButtonItem?.isEnabled = true + navigationItem.leftBarButtonItem?.isEnabled = true + } + } + } @objc private func cancel(_ sender: UIBarButtonItem) { diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 12dd976fc0..e5a6d6cfa5 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -118,9 +118,15 @@ extension NotificationViewController { //MARK: - Actions @objc private func showNotificationPolicySettings(_ sender: Any) { - guard let policy = viewModel?.notificationPolicy else { return } + guard let viewModel, let policy = viewModel.notificationPolicy else { return } - let policyViewModel = NotificationFilterViewModel(notFollowing: policy.filterNotFollowing, noFollower: policy.filterNotFollowers, newAccount: policy.filterNewAccounts, privateMentions: policy.filterPrivateMentions) + let policyViewModel = NotificationFilterViewModel( + appContext: viewModel.context, + notFollowing: policy.filterNotFollowing, + noFollower: policy.filterNotFollowers, + newAccount: policy.filterNewAccounts, + privateMentions: policy.filterPrivateMentions + ) //TODO: Move to SceneCoordinator let notificationPolicyViewController = NotificationPolicyViewController(viewModel: policyViewModel) notificationPolicyViewController.modalPresentationStyle = .formSheet diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index b66e8fe23c..4453e8e6ae 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -113,7 +113,28 @@ extension APIService { let domain = authenticationBox.domain let authorization = authenticationBox.userAuthorization - let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization).singleOutput() + let response = try await Mastodon.API.Notifications.getNotificationPolicy(session: session, domain: domain, authorization: authorization) + + return response + } + + public func updateNotificationPolicy( + authenticationBox: MastodonAuthenticationBox, + filterNotFollowing: Bool, + filterNotFollowers: Bool, + filterNewAccounts: Bool, + filterPrivateMentions: Bool + ) async throws -> Mastodon.Response.Content { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + let query = Mastodon.API.Notifications.UpdateNotificationPolicyQuery(filterNotFollowing: filterNotFollowing, filterNotFollowers: filterNotFollowers, filterNewAccounts: filterNewAccounts, filterPrivateMentions: filterPrivateMentions) + + let response = try await Mastodon.API.Notifications.updateNotificationPolicy( + session: session, + domain: domain, + authorization: authorization, + query: query + ) return response } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index f10740b2a5..5df6c8e8df 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -142,20 +142,57 @@ extension Mastodon.API.Notifications { notificationsEndpointURL(domain: domain).appendingPathComponent("policy") } + public struct UpdateNotificationPolicyQuery: Codable, PatchQuery { + public let filterNotFollowing: Bool + public let filterNotFollowers: Bool + public let filterNewAccounts: Bool + public let filterPrivateMentions: Bool + + enum CodingKeys: String, CodingKey { + case filterNotFollowing = "filter_not_following" + case filterNotFollowers = "filter_not_followers" + case filterNewAccounts = "filter_new_accounts" + case filterPrivateMentions = "filter_private_mentions" + } + + public init(filterNotFollowing: Bool, filterNotFollowers: Bool, filterNewAccounts: Bool, filterPrivateMentions: Bool) { + self.filterNotFollowing = filterNotFollowing + self.filterNotFollowers = filterNotFollowers + self.filterNewAccounts = filterNewAccounts + self.filterPrivateMentions = filterPrivateMentions + } + } + public static func getNotificationPolicy( session: URLSession, domain: String, authorization: Mastodon.API.OAuth.Authorization - ) -> AnyPublisher, Error> { + ) async throws -> Mastodon.Response.Content { let request = Mastodon.API.get( url: notificationPolicyEndpointURL(domain: domain), authorization: authorization ) - return session.dataTaskPublisher(for: request) - .tryMap { data, response in - let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) - return Mastodon.Response.Content(value: value, response: response) - } - .eraseToAnyPublisher() + + let (data, response) = try await session.data(for: request) + + let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + + public static func updateNotificationPolicy( + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization, + query: Mastodon.API.Notifications.UpdateNotificationPolicyQuery + ) async throws -> Mastodon.Response.Content { + let request = Mastodon.API.patch( + url: notificationPolicyEndpointURL(domain: domain), + query: query, + authorization: authorization + ) + let (data, response) = try await session.data(for: request) + let value = try Mastodon.API.decode(type: Mastodon.Entity.NotificationPolicy.self, from: data, response: response) + + return Mastodon.Response.Content(value: value, response: response) } } diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift index f93d8af1b9..caaf6a02ab 100644 --- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift +++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift @@ -55,6 +55,11 @@ extension PostQuery { // PATCH protocol PatchQuery: RequestQuery { } +extension PatchQuery { + // By default a `PostQuery` does not have query items + var queryItems: [URLQueryItem]? { nil } +} + // PUT protocol PutQuery: RequestQuery { } From 7cdde7797f50327b29fffa53f6f5b94ca3547f7a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 17:51:33 +0200 Subject: [PATCH 10/48] Download notification-requests (IOS-241) --- .../Service/API/APIService+Notification.swift | 15 ++++++++++++ .../API/Mastodon+API+Notifications.swift | 22 ++++++++++++++++++ .../Mastodon+Entity+NotificationRequest.swift | 23 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index 4453e8e6ae..bb944ac7a6 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -108,6 +108,8 @@ extension APIService { } +//MARK: - Notification Policy + extension APIService { public func notificationPolicy(authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Response.Content { let domain = authenticationBox.domain @@ -139,3 +141,16 @@ extension APIService { return response } } + +//MARK: - Notification Requests + +extension APIService { + public func notificationRequests(authenticationBox: MastodonAuthenticationBox) async throws -> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]> { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Notifications.getNotificationRequests(session: session, domain: domain, authorization: authorization) + + return response + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index 5df6c8e8df..d309e6cf5a 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -196,3 +196,25 @@ extension Mastodon.API.Notifications { return Mastodon.Response.Content(value: value, response: response) } } + +extension Mastodon.API.Notifications { + internal static func notificationRequestsEndpointURL(domain: String) -> URL { + notificationsEndpointURL(domain: domain).appendingPathComponent("requests") + } + + public static func getNotificationRequests( + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.NotificationRequest]> { + let request = Mastodon.API.get( + url: notificationRequestsEndpointURL(domain: domain), + authorization: authorization + ) + + let (data, response) = try await session.data(for: request) + + let value = try Mastodon.API.decode(type: [Mastodon.Entity.NotificationRequest].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } +} diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift new file mode 100644 index 0000000000..44be2ab0a0 --- /dev/null +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift @@ -0,0 +1,23 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import Foundation + +extension Mastodon.Entity { + public struct NotificationRequest: Codable, Hashable { + public let id: String + public let createdAt: Date + public let updatedAt: Date + public let fromAccount: Mastodon.Entity.Account + public let notificationsCount: Int + public let lastStatus: Mastodon.Entity.Status? + + enum CodingKeys: String, CodingKey { + case id = "id" + case createdAt = "created_at" + case updatedAt = "updated_at" + case fromAccount = "from_account" + case notificationsCount = "notifications_count" + case lastStatus = "last_status" + } + } +} From 5473e7f3f94fbe4367537faa78a2cd93cd86fc9d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 17:52:27 +0200 Subject: [PATCH 11/48] Add viewController for notification-requests (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 8 ++++++ Mastodon/Coordinator/SceneCoordinator.swift | 7 +++++- .../DataSourceFacade+Notifications.swift | 25 +++++++++++++++++++ ...taSourceProvider+UITableViewDelegate.swift | 3 +-- .../NotificationPolicyViewController.swift | 2 +- ...ificationRequestsTableViewController.swift | 25 +++++++++++++++++++ .../NotificationViewController.swift | 2 +- 7 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift create mode 100644 Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 07c93f2d3e..59298729cd 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -161,6 +161,8 @@ D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; }; D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; }; D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */; }; + D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */; }; + D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -796,6 +798,8 @@ D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = ""; }; D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; }; D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; }; + D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; + D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1770,6 +1774,7 @@ children = ( D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */, + D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, ); path = "Notification Filtering"; sourceTree = ""; @@ -2424,6 +2429,7 @@ DB0FCB67279507EF006C02E2 /* DataSourceFacade+Meta.swift */, 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */, DB0FCB79279576A2006C02E2 /* DataSourceFacade+Thread.swift */, + D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */, DB63F74627990B0600455B82 /* DataSourceFacade+Hashtag.swift */, DB63F7532799491600455B82 /* DataSourceFacade+SearchHistory.swift */, DB159C2A27A17BAC0068DC77 /* DataSourceFacade+Media.swift */, @@ -3514,6 +3520,7 @@ DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */, DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, + D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */, D8FAAE432AD047B200DC1832 /* AboutInstanceTableFooterView.swift in Sources */, D808B94E296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift in Sources */, D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */, @@ -3868,6 +3875,7 @@ DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */, 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */, DB0FCB882796BDA9006C02E2 /* SearchItem.swift in Sources */, + D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */, DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */, 2A409F832B5955290044E472 /* MastodonStatusThreadViewModel+State.swift in Sources */, 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index acf985feef..0e9ff6f711 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -205,7 +205,10 @@ extension SceneCoordinator { // setting case settings(setting: Setting) - + + // Notifications + case notificationRequests(viewModel: NotificationRequestsViewModel) + // report case report(viewModel: ReportViewModel) case reportServerRules(viewModel: ReportServerRulesViewModel) @@ -558,6 +561,8 @@ private extension SceneCoordinator { case .editStatus(let viewModel): let composeViewController = ComposeViewController(viewModel: viewModel) viewController = composeViewController + case .notificationRequests(let viewModel): + viewController = NotificationRequestsTableViewController(viewModel: viewModel) } setupDependency(for: viewController as? NeedsDependency) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift new file mode 100644 index 0000000000..937f75eff0 --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -0,0 +1,25 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonCore + +extension DataSourceFacade { + @MainActor + static func coordinateToNotificationRequests( + provider: DataSourceProvider & AuthContextProvider + ) async { + provider.coordinator.showLoading() + + do { + let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox) + let viewModel = NotificationRequestsViewModel() + + provider.coordinator.hideLoading() + + provider.coordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: .show) + } catch { + //TODO: Error Handling + provider.coordinator.hideLoading() + } + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift index 712a169703..bea5118e4e 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift @@ -58,8 +58,7 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid ) } case .notificationBanner(let policy): - //TODO: Coordinate to pending notification-screen - break + await DataSourceFacade.coordinateToNotificationRequests(provider: self) } } } diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift index 781aba6cd3..aa599c1e47 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift @@ -150,7 +150,7 @@ class NotificationPolicyViewController: UIViewController { guard let self else { return } do { - let result = try await viewModel.appContext.apiService.updateNotificationPolicy( + _ = try await viewModel.appContext.apiService.updateNotificationPolicy( authenticationBox: authenticationBox, filterNotFollowing: viewModel.notFollowing, filterNotFollowers: viewModel.noFollower, diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift new file mode 100644 index 0000000000..42f121b9f3 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift @@ -0,0 +1,25 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit + +struct NotificationRequestsViewModel { + +} + +class NotificationRequestsTableViewController: UIViewController { + let tableView: UITableView + + init(viewModel: NotificationRequestsViewModel) { + //TODO: Cell, DataSource, Delegate.... + tableView = UITableView(frame: .zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.backgroundColor = .systemBackground + + super.init(nibName: nil, bundle: nil) + + view.addSubview(tableView) + tableView.pinToParent() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } +} diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index e5a6d6cfa5..d7d53e2ec1 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -127,7 +127,7 @@ extension NotificationViewController { newAccount: policy.filterNewAccounts, privateMentions: policy.filterPrivateMentions ) - //TODO: Move to SceneCoordinator + //TODO: Move to SceneCoordinator, we'd need a new case for this let notificationPolicyViewController = NotificationPolicyViewController(viewModel: policyViewModel) notificationPolicyViewController.modalPresentationStyle = .formSheet let navigationController = UINavigationController(rootViewController: notificationPolicyViewController) From b119a6db1fa9e1810abf3cb18acf37040781d243 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 18:29:05 +0200 Subject: [PATCH 12/48] Use correct properties for account (IOS-241) --- .../Entity/Mastodon+Entity+NotificationRequest.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift index 44be2ab0a0..2f6188b7ea 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+NotificationRequest.swift @@ -7,15 +7,15 @@ extension Mastodon.Entity { public let id: String public let createdAt: Date public let updatedAt: Date - public let fromAccount: Mastodon.Entity.Account - public let notificationsCount: Int + public let account: Mastodon.Entity.Account + public let notificationsCount: String // contains an `Int` public let lastStatus: Mastodon.Entity.Status? enum CodingKeys: String, CodingKey { case id = "id" case createdAt = "created_at" case updatedAt = "updated_at" - case fromAccount = "from_account" + case account = "account" case notificationsCount = "notifications_count" case lastStatus = "last_status" } From 05a08f6ff233f98036b0c191f572e6b518ca8d36 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 19:10:24 +0200 Subject: [PATCH 13/48] Use better icon after feedback (IOS-241) --- Mastodon/Scene/Notification/NotificationViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index d7d53e2ec1..46fee81d01 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -88,7 +88,7 @@ extension NotificationViewController { // https://github.com/mastodon/documentation/pull/1447#issuecomment-2149225659 if let viewModel, viewModel.notificationPolicy != nil { - navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease"), style: .plain, target: self, action: #selector(NotificationViewController.showNotificationPolicySettings(_:))) + navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease.circle"), style: .plain, target: self, action: #selector(NotificationViewController.showNotificationPolicySettings(_:))) } // needs trigger manually after onboarding dismiss From 99f2fc923693f29e363517e408e8d0dd50542e50 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 19:59:35 +0200 Subject: [PATCH 14/48] More feedback from Sam (IOS-241) --- ...ficationFilteringBannerTableViewCell.swift | 83 +++++++++++++++++-- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index 409673e2eb..9eb3d5b43b 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -2,21 +2,90 @@ import UIKit import MastodonSDK +import MastodonUI class NotificationFilteringBannerTableViewCell: UITableViewCell { + static let reuseIdentifier = "NotificationFilteringBannerTableViewCell" - //TODO: Add separator + let iconImageView: UIImageView + let iconImageWrapperView: UIView - func configure(with policy: Mastodon.Entity.NotificationPolicy) { - var configuration = defaultContentConfiguration() + let titleLabel: UILabel + let subtitleLabel: UILabel + private let contentStackView: UIStackView + private let labelStackView: UIStackView + let separatorLine: UIView + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + + iconImageView = UIImageView(image: UIImage(systemName: "archivebox")) + iconImageView.translatesAutoresizingMaskIntoConstraints = false + iconImageWrapperView = UIView() + iconImageWrapperView.translatesAutoresizingMaskIntoConstraints = false + iconImageWrapperView.addSubview(iconImageView) + + titleLabel = UILabel() //TODO: Add localization - configuration.text = "Filtered notifications" - configuration.secondaryText = "\(policy.summary.pendingRequestsCount) people you may know" - configuration.image = UIImage(systemName: "archivebox") + titleLabel.text = "Filtered Notifications" + titleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) + + subtitleLabel = UILabel() + subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) + subtitleLabel.textColor = .secondaryLabel + + + labelStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + labelStackView.translatesAutoresizingMaskIntoConstraints = false + labelStackView.alignment = .leading + labelStackView.axis = .vertical + + contentStackView = UIStackView(arrangedSubviews: [iconImageWrapperView, labelStackView]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.alignment = .center + contentStackView.axis = .horizontal + contentStackView.spacing = 12 + + separatorLine = UIView.separatorLine + separatorLine.translatesAutoresizingMaskIntoConstraints = false + + super.init(style: style, reuseIdentifier: reuseIdentifier) - self.contentConfiguration = configuration + contentView.addSubview(contentStackView) + contentView.addSubview(separatorLine) + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + + iconImageWrapperView.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width), + iconImageWrapperView.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.defaultHigh), + iconImageView.centerXAnchor.constraint(equalTo: iconImageWrapperView.centerXAnchor), + iconImageView.centerYAnchor.constraint(equalTo: iconImageWrapperView.centerYAnchor), + iconImageView.widthAnchor.constraint(equalToConstant: 27), + iconImageView.heightAnchor.constraint(equalToConstant: 24), + + contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16), + separatorLine.topAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 7), + + separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)) + ] + + NSLayoutConstraint.activate(constraints) + } + + func configure(with policy: Mastodon.Entity.NotificationPolicy) { + //TODO: Add localization + subtitleLabel.text = "\(policy.summary.pendingRequestsCount) people you may know" } } From ed4a003a743154fac0b19829229402ef6265902a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 9 Jul 2024 20:04:25 +0200 Subject: [PATCH 15/48] Style cells according to Sams feedback (IOS-241) --- .../NotificationPolicyFilterTableViewCell.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift index f07f1b0668..76950c2139 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift @@ -17,8 +17,9 @@ class NotificationPolicyFilterTableViewCell: ToggleTableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold)) + 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(NotificationPolicyFilterTableViewCell.toggleValueChanged(_:)), for: .valueChanged) } From c79e26b7e598af53a1b78a00af69ecff4fcf54ae Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 10 Jul 2024 07:38:13 +0200 Subject: [PATCH 16/48] Refactor code to coordinator (IOS-241) --- Mastodon/Coordinator/SceneCoordinator.swift | 3 +++ .../Notification/NotificationViewController.swift | 10 +--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 0e9ff6f711..e80657a171 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -207,6 +207,7 @@ extension SceneCoordinator { case settings(setting: Setting) // Notifications + case notificationPolicy(viewModel: NotificationFilterViewModel) case notificationRequests(viewModel: NotificationRequestsViewModel) // report @@ -563,6 +564,8 @@ private extension SceneCoordinator { viewController = composeViewController case .notificationRequests(let viewModel): viewController = NotificationRequestsTableViewController(viewModel: viewModel) + case .notificationPolicy(let viewModel): + viewController = NotificationPolicyViewController(viewModel: viewModel) } setupDependency(for: viewController as? NeedsDependency) diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 46fee81d01..cc18c80a18 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -127,16 +127,8 @@ extension NotificationViewController { newAccount: policy.filterNewAccounts, privateMentions: policy.filterPrivateMentions ) - //TODO: Move to SceneCoordinator, we'd need a new case for this - let notificationPolicyViewController = NotificationPolicyViewController(viewModel: policyViewModel) - notificationPolicyViewController.modalPresentationStyle = .formSheet - let navigationController = UINavigationController(rootViewController: notificationPolicyViewController) - if let sheet = navigationController.sheetPresentationController { - sheet.detents = [.medium(), .large()] - } - - present(navigationController, animated: true) + _ = coordinator.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) } } From 6702f0624c20390cce9338294499e58658c85e90 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 10 Jul 2024 12:58:21 +0200 Subject: [PATCH 17/48] Implement special sheet header (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 16 ++++++- ...otificationPolicyFilterTableViewCell.swift | 0 .../Policy/NotificationPolicyHeaderView.swift | 46 +++++++++++++++++++ .../NotificationPolicyViewController.swift | 39 +++++++--------- 4 files changed, 76 insertions(+), 25 deletions(-) rename Mastodon/Scene/Notification/Notification Filtering/{ => Policy}/NotificationPolicyFilterTableViewCell.swift (100%) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift rename Mastodon/Scene/Notification/Notification Filtering/{ => Policy}/NotificationPolicyViewController.swift (83%) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 59298729cd..04ada8e65e 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -163,6 +163,7 @@ D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */; }; D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */; }; D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */; }; + D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -800,6 +801,7 @@ D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; }; D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; + D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyHeaderView.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1772,8 +1774,7 @@ D80EC2602C2978CB009724A5 /* Notification Filtering */ = { isa = PBXGroup; children = ( - D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, - D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */, + D84BB7702C3E565100493718 /* Policy */, D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, ); path = "Notification Filtering"; @@ -1828,6 +1829,16 @@ path = Shared; sourceTree = ""; }; + D84BB7702C3E565100493718 /* Policy */ = { + isa = PBXGroup; + children = ( + D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, + D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */, + D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */, + ); + path = Policy; + sourceTree = ""; + }; D84C099E2B0F9E41009E685E /* Documentation */ = { isa = PBXGroup; children = ( @@ -3779,6 +3790,7 @@ DB0FCB822796AC78006C02E2 /* UserTimelineViewController+DataSourceProvider.swift in Sources */, DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */, DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */, + D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */, 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyFilterTableViewCell.swift similarity index 100% rename from Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyFilterTableViewCell.swift rename to Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyFilterTableViewCell.swift diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift new file mode 100644 index 0000000000..f5a93733d6 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift @@ -0,0 +1,46 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit + +class NotificationPolicyHeaderView: UIView { + let titleLabel: UILabel + let closeButton: UIButton + + override init(frame: CGRect) { + + titleLabel = UILabel() + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: .systemFont(ofSize: 20, weight: .bold)) + // TODO: Localization + titleLabel.text = "Filter Notifications from..." + + closeButton = UIButton() + closeButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + closeButton.translatesAutoresizingMaskIntoConstraints = false + closeButton.setInsets(forContentPadding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), imageTitlePadding: 0) + closeButton.contentMode = .center + + super.init(frame: frame) + addSubview(titleLabel) + addSubview(closeButton) + + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20), + closeButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 8), + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor), + + closeButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + trailingAnchor.constraint(equalTo: closeButton.trailingAnchor, constant: 20), + bottomAnchor.constraint(greaterThanOrEqualTo: closeButton.bottomAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift similarity index 83% rename from Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift rename to Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift index aa599c1e47..51577c7b46 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift @@ -64,6 +64,7 @@ struct NotificationFilterViewModel { class NotificationPolicyViewController: UIViewController { let tableView: UITableView + let headerBar: NotificationPolicyHeaderView var saveItem: UIBarButtonItem? var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] @@ -73,6 +74,9 @@ class NotificationPolicyViewController: UIViewController { self.viewModel = viewModel items = NotificationFilterItem.allCases + headerBar = NotificationPolicyHeaderView() + headerBar.translatesAutoresizingMaskIntoConstraints = false + tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.register(NotificationPolicyFilterTableViewCell.self, forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier) @@ -91,19 +95,14 @@ class NotificationPolicyViewController: UIViewController { return cell } - // TODO: Localization - title = "Filter Notifications from" - tableView.dataSource = dataSource tableView.delegate = self self.dataSource = dataSource + view.addSubview(headerBar) view.addSubview(tableView) view.backgroundColor = .systemGroupedBackground - - saveItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.save, style: .done, target: self, action: #selector(NotificationPolicyViewController.save(_:))) - navigationItem.rightBarButtonItem = saveItem - navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .done, target: self, action: #selector(NotificationPolicyViewController.cancel(_:))) + headerBar.closeButton.addTarget(self, action: #selector(NotificationPolicyViewController.save(_:)), for: .touchUpInside) setupConstraints() } @@ -123,7 +122,11 @@ class NotificationPolicyViewController: UIViewController { private func setupConstraints() { let constraints = [ - tableView.topAnchor.constraint(equalTo: view.topAnchor), + headerBar.topAnchor.constraint(equalTo: view.topAnchor), + headerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: headerBar.trailingAnchor), + + tableView.topAnchor.constraint(equalTo: headerBar.bottomAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor), view.bottomAnchor.constraint(equalTo: tableView.bottomAnchor), @@ -134,17 +137,11 @@ class NotificationPolicyViewController: UIViewController { // MARK: - Action - @objc private func save(_ sender: UIBarButtonItem) { + @objc private func save(_ sender: UIButton) { guard let authenticationBox = viewModel.appContext.authenticationService.mastodonAuthenticationBoxes.first else { return } - navigationItem.leftBarButtonItem?.isEnabled = false - - let activityIndicator = UIActivityIndicatorView(style: .medium) - - navigationItem.rightBarButtonItem = UIBarButtonItem(customView: activityIndicator) - navigationItem.rightBarButtonItem?.isEnabled = false - - activityIndicator.startAnimating() + //TODO: Check if this really works. Garbage collection and stuff + self.dismiss(animated:true) Task { [weak self] in guard let self else { return } @@ -158,13 +155,9 @@ class NotificationPolicyViewController: UIViewController { filterPrivateMentions: viewModel.privateMentions ) - await MainActor.run { - self.dismiss(animated:true) - } + } catch { - navigationItem.rightBarButtonItem = saveItem - navigationItem.rightBarButtonItem?.isEnabled = true - navigationItem.leftBarButtonItem?.isEnabled = true + //TODO: Error Handling } } From 65075c540421bca65d668d58f8364ea751f89302 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 10 Jul 2024 13:34:57 +0200 Subject: [PATCH 18/48] "Fix" content inset (IOS-241) --- .../Policy/NotificationPolicyViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift index 51577c7b46..b64fdb9d85 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift @@ -80,6 +80,7 @@ class NotificationPolicyViewController: UIViewController { tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.register(NotificationPolicyFilterTableViewCell.self, forCellReuseIdentifier: NotificationPolicyFilterTableViewCell.reuseIdentifier) + tableView.contentInset.top = -20 super.init(nibName: nil, bundle: nil) @@ -168,6 +169,8 @@ class NotificationPolicyViewController: UIViewController { } } +//MARK: - UITableViewDelegate + extension NotificationPolicyViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) From 7c6c9b5e7963b6e1dfd6bb68463e469a1012307c Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 10 Jul 2024 13:52:56 +0200 Subject: [PATCH 19/48] Use SymbolConfigurations for buttons (IOS-241) I feel so stupid from time to time --- .../NotificationFilteringBannerTableViewCell.swift | 6 +++--- .../Policy/NotificationPolicyHeaderView.swift | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index 9eb3d5b43b..8892c12d58 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -19,7 +19,9 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - iconImageView = UIImageView(image: UIImage(systemName: "archivebox")) + let iconConfiguration = UIImage.SymbolConfiguration(scale: .large) + let icon = UIImage(systemName: "archivebox", withConfiguration: iconConfiguration) + iconImageView = UIImageView(image: icon) iconImageView.translatesAutoresizingMaskIntoConstraints = false iconImageWrapperView = UIView() @@ -67,8 +69,6 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { iconImageWrapperView.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.defaultHigh), iconImageView.centerXAnchor.constraint(equalTo: iconImageWrapperView.centerXAnchor), iconImageView.centerYAnchor.constraint(equalTo: iconImageWrapperView.centerYAnchor), - iconImageView.widthAnchor.constraint(equalToConstant: 27), - iconImageView.heightAnchor.constraint(equalToConstant: 24), contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift index f5a93733d6..2d6e2fa776 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift @@ -14,10 +14,15 @@ class NotificationPolicyHeaderView: UIView { // TODO: Localization titleLabel.text = "Filter Notifications from..." - closeButton = UIButton() - closeButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + + let buttonImageConfiguration = UIImage.SymbolConfiguration(scale: .large) + let buttonImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: buttonImageConfiguration) + var buttonConfiguration = UIButton.Configuration.plain() + buttonConfiguration.image = buttonImage + buttonConfiguration.contentInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10) + + closeButton = UIButton(configuration: buttonConfiguration) closeButton.translatesAutoresizingMaskIntoConstraints = false - closeButton.setInsets(forContentPadding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), imageTitlePadding: 0) closeButton.contentMode = .center super.init(frame: frame) From 4cf75d307dd27ce65cdcc01c86f93cc636766ca9 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 10 Jul 2024 23:03:21 +0200 Subject: [PATCH 20/48] Show account for request (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 14 ++- .../DataSourceFacade+Notifications.swift | 4 +- ...ificationRequestsTableViewController.swift | 25 ------ .../NotificationRequestTableViewCell.swift | 90 +++++++++++++++++++ ...ificationRequestsTableViewController.swift | 70 +++++++++++++++ 5 files changed, 175 insertions(+), 28 deletions(-) delete mode 100644 Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 04ada8e65e..49029612ad 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */; }; D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */; }; D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */; }; + D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -802,6 +803,7 @@ D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyHeaderView.swift; sourceTree = ""; }; + D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1774,8 +1776,8 @@ D80EC2602C2978CB009724A5 /* Notification Filtering */ = { isa = PBXGroup; children = ( + D84BB7732C3EB7F600493718 /* Requests */, D84BB7702C3E565100493718 /* Policy */, - D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, ); path = "Notification Filtering"; sourceTree = ""; @@ -1839,6 +1841,15 @@ path = Policy; sourceTree = ""; }; + D84BB7732C3EB7F600493718 /* Requests */ = { + isa = PBXGroup; + children = ( + D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, + D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */, + ); + path = Requests; + sourceTree = ""; + }; D84C099E2B0F9E41009E685E /* Documentation */ = { isa = PBXGroup; children = ( @@ -3620,6 +3631,7 @@ D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */, DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */, + D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */, DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index 937f75eff0..d465fdffa6 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -11,8 +11,8 @@ extension DataSourceFacade { provider.coordinator.showLoading() do { - let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox) - let viewModel = NotificationRequestsViewModel() + let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox).value + let viewModel = NotificationRequestsViewModel(requests: notificationRequests) provider.coordinator.hideLoading() diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift deleted file mode 100644 index 42f121b9f3..0000000000 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2024 Mastodon gGmbH. All rights reserved. - -import UIKit - -struct NotificationRequestsViewModel { - -} - -class NotificationRequestsTableViewController: UIViewController { - let tableView: UITableView - - init(viewModel: NotificationRequestsViewModel) { - //TODO: Cell, DataSource, Delegate.... - tableView = UITableView(frame: .zero) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.backgroundColor = .systemBackground - - super.init(nibName: nil, bundle: nil) - - view.addSubview(tableView) - tableView.pinToParent() - } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } -} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift new file mode 100644 index 0000000000..4b56751f9c --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -0,0 +1,90 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonSDK +import MetaTextKit +import MastodonMeta +import MastodonUI +import MastodonCore + +class NotificationRequestTableViewCell: UITableViewCell { + static let reuseIdentifier = "NotificationRequestTableViewCell" + + let nameLabel: MetaLabel + let usernameLabel: MetaLabel + let avatarButton: AvatarButton + + private let labelStackView: UIStackView + private let contentStackView: UIStackView + +// private let stack + // accept/deny-button + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + nameLabel = MetaLabel(style: .statusName) + usernameLabel = MetaLabel(style: .statusUsername) + avatarButton = AvatarButton() + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.size = CGSize.authorAvatarButtonSize + avatarButton.avatarImageView.imageViewSize = CGSize.authorAvatarButtonSize + + labelStackView = UIStackView(arrangedSubviews: [nameLabel, usernameLabel]) + labelStackView.axis = .vertical + labelStackView.alignment = .leading + labelStackView.spacing = 4 + + contentStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.axis = .horizontal + contentStackView.alignment = .center + contentStackView.spacing = 12 + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubview(contentStackView) + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + + contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16), + contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + + avatarButton.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width).priority(.required - 1), + avatarButton.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.required - 1), + ] + NSLayoutConstraint.activate(constraints) + + } + + override func prepareForReuse() { + avatarButton.avatarImageView.image = nil + avatarButton.avatarImageView.cancelTask() + } + + func configure(with request: Mastodon.Entity.NotificationRequest) { + let account = request.account + + avatarButton.avatarImageView.configure(with: account.avatarImageURL()) + avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) + + // author name + let metaAccountName: MetaContent + do { + let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis.asDictionary) + metaAccountName = try MastodonMetaContent.convert(document: content) + } catch { + assertionFailure(error.localizedDescription) + metaAccountName = PlaintextMetaContent(string: account.displayNameWithFallback) + } + nameLabel.configure(content: metaAccountName) + + let metaUsername = PlaintextMetaContent(string: "@\(account.acct)") + usernameLabel.configure(content: metaUsername) + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift new file mode 100644 index 0000000000..54797e60a9 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -0,0 +1,70 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonSDK + +struct NotificationRequestsViewModel { + var requests: [Mastodon.Entity.NotificationRequest] +} + +enum NotificationRequestsSection: Hashable { + case main +} + +enum NotificationRequestItem: Hashable { + case item(Mastodon.Entity.NotificationRequest) +} + +class NotificationRequestsTableViewController: UIViewController { + + let tableView: UITableView + var viewModel: NotificationRequestsViewModel + var dataSource: UITableViewDiffableDataSource? + + init(viewModel: NotificationRequestsViewModel) { + //TODO: DataSource, Delegate.... + self.viewModel = viewModel + + tableView = UITableView(frame: .zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.backgroundColor = .systemBackground + tableView.register(NotificationRequestTableViewCell.self, forCellReuseIdentifier: NotificationRequestTableViewCell.reuseIdentifier) + + super.init(nibName: nil, bundle: nil) + + view.addSubview(tableView) + tableView.pinToParent() + + let dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in + guard let cell = tableView.dequeueReusableCell(withIdentifier: NotificationRequestTableViewCell.reuseIdentifier, for: indexPath) as? NotificationRequestTableViewCell else { + fatalError("No NotificationRequestTableViewCell") + } + + let request = viewModel.requests[indexPath.row] + cell.configure(with: request) + + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self + self.dataSource = dataSource + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + dataSource?.apply(snapshot) + } +} + +extension NotificationRequestsTableViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } +} From 0d5dd513ddef009f9a23ecbd784e1f98b5838f31 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 Jul 2024 17:16:18 +0200 Subject: [PATCH 21/48] [WIP] Prepare enum-cases to show notifications from one account (IOS-241) --- Mastodon/Coordinator/SceneCoordinator.swift | 6 ++++- .../DataSourceFacade+Notifications.swift | 23 +++++++++++++++++- ...ificationRequestsTableViewController.swift | 24 +++++++++++++++++-- ...ionTimelineViewModel+LoadOldestState.swift | 3 ++- .../NotificationTimelineViewModel.swift | 20 +++++++++++++--- .../NotificationViewController.swift | 4 ++-- .../Notification/NotificationViewModel.swift | 2 ++ .../DataController/FeedDataController.swift | 4 +++- .../Service/API/APIService+Notification.swift | 24 +------------------ .../Sources/MastodonSDK/MastodonFeed.swift | 1 + 10 files changed, 77 insertions(+), 34 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index e80657a171..7bfd4208bd 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -209,6 +209,7 @@ extension SceneCoordinator { // Notifications case notificationPolicy(viewModel: NotificationFilterViewModel) case notificationRequests(viewModel: NotificationRequestsViewModel) + case notificationtimeline(viewModel: NotificationTimelineViewModel) // report case report(viewModel: ReportViewModel) @@ -563,9 +564,12 @@ private extension SceneCoordinator { let composeViewController = ComposeViewController(viewModel: viewModel) viewController = composeViewController case .notificationRequests(let viewModel): - viewController = NotificationRequestsTableViewController(viewModel: viewModel) + //FIXME: Put either AuthContext or Everything in ViewModel + viewController = NotificationRequestsTableViewController(viewModel: viewModel, appContext: appContext, coordinator: self) case .notificationPolicy(let viewModel): viewController = NotificationPolicyViewController(viewModel: viewModel) + case .notificationtimeline(let viewModel): + viewController = NotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self) } setupDependency(for: viewController as? NeedsDependency) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index d465fdffa6..cb2d748e24 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -2,6 +2,7 @@ import Foundation import MastodonCore +import MastodonSDK extension DataSourceFacade { @MainActor @@ -12,7 +13,7 @@ extension DataSourceFacade { do { let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox).value - let viewModel = NotificationRequestsViewModel(requests: notificationRequests) + let viewModel = NotificationRequestsViewModel(requests: notificationRequests, authContext: provider.authContext) provider.coordinator.hideLoading() @@ -22,4 +23,24 @@ extension DataSourceFacade { provider.coordinator.hideLoading() } } + + @MainActor + static func coordinateToNotificationRequest( + request: Mastodon.Entity.NotificationRequest, + provider: ViewControllerWithDependencies & AuthContextProvider + ) async { + provider.coordinator.showLoading() + + do { + + // load notifications for request.account + // show NotificationTimelineViewController with NotificationTimelineViewModel + + provider.coordinator.hideLoading() + } catch { + //TODO: Error Handling + provider.coordinator.hideLoading() + } + } + } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 54797e60a9..0b0a80491a 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -2,9 +2,11 @@ import UIKit import MastodonSDK +import MastodonCore struct NotificationRequestsViewModel { var requests: [Mastodon.Entity.NotificationRequest] + let authContext: AuthContext } enum NotificationRequestsSection: Hashable { @@ -15,15 +17,21 @@ enum NotificationRequestItem: Hashable { case item(Mastodon.Entity.NotificationRequest) } -class NotificationRequestsTableViewController: UIViewController { +class NotificationRequestsTableViewController: UIViewController, NeedsDependency { + + var context: AppContext! + var coordinator: SceneCoordinator! + let tableView: UITableView var viewModel: NotificationRequestsViewModel var dataSource: UITableViewDiffableDataSource? - init(viewModel: NotificationRequestsViewModel) { + init(viewModel: NotificationRequestsViewModel, appContext: AppContext, coordinator: SceneCoordinator) { //TODO: DataSource, Delegate.... self.viewModel = viewModel + self.context = appContext + self.coordinator = coordinator tableView = UITableView(frame: .zero) tableView.translatesAutoresizingMaskIntoConstraints = false @@ -63,8 +71,20 @@ class NotificationRequestsTableViewController: UIViewController { } } +// MARK: - UITableViewDelegate extension NotificationRequestsTableViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + + let request = viewModel.requests[indexPath.row] + + Task { + await DataSourceFacade.coordinateToNotificationRequest(request: request, provider: self) + } } } + +// MARK: - AuthContextProvider +extension NotificationRequestsTableViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift index 0999618569..6c9ea12103 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift @@ -64,7 +64,8 @@ extension NotificationTimelineViewModel.LoadOldestState { do { let response = try await viewModel.context.apiService.notifications( maxID: maxID, - scope: scope, + //FIXME: Use correct scope for accounts + scope: .everything, authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 6282c37739..c42bbe6be5 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -65,6 +65,9 @@ final class NotificationTimelineViewModel { self.dataController.records = (try? FileManager.default.cachedNotificationsMentions(for: authContext.mastodonAuthenticationBox))?.map({ notification in MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions) }) ?? [] + case .fromAccount(let account): + //TODO: Implement + self.dataController.records = [] } self.dataController.$records @@ -80,6 +83,9 @@ final class NotificationTimelineViewModel { FileManager.default.cacheNotificationsAll(items: items, for: authContext.mastodonAuthenticationBox) case .mentions: FileManager.default.cacheNotificationsMentions(items: items, for: authContext.mastodonAuthenticationBox) + case .fromAccount(_): + //TODO: Implement + break } }) .store(in: &disposeBag) @@ -89,9 +95,11 @@ final class NotificationTimelineViewModel { } extension NotificationTimelineViewModel { - - typealias Scope = APIService.MastodonNotificationScope - + enum Scope: Hashable { + case everything + case mentions + case fromAccount(Mastodon.Entity.Account) + } } extension NotificationTimelineViewModel { @@ -106,6 +114,9 @@ extension NotificationTimelineViewModel { dataController.loadInitial(kind: .notificationAll) case .mentions: dataController.loadInitial(kind: .notificationMentions) + case .fromAccount(_): + //TODO: Implement + break } didLoadLatest.send() @@ -118,6 +129,9 @@ extension NotificationTimelineViewModel { dataController.loadNext(kind: .notificationAll) case .mentions: dataController.loadNext(kind: .notificationMentions) + case .fromAccount(_): + //TODO: Implement + break } } } diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index cc18c80a18..b2d943ad26 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -49,7 +49,7 @@ extension NotificationViewController { view.backgroundColor = .secondarySystemBackground - setupSegmentedControl(scopes: APIService.MastodonNotificationScope.allCases) + setupSegmentedControl(scopes: [.everything, .mentions]) pageSegmentedControl.translatesAutoresizingMaskIntoConstraints = false navigationItem.titleView = pageSegmentedControl NSLayoutConstraint.activate([ @@ -68,7 +68,7 @@ extension NotificationViewController { } .store(in: &disposeBag) - viewModel?.viewControllers = APIService.MastodonNotificationScope.allCases.map { scope in + viewModel?.viewControllers = [NotificationTimelineViewModel.Scope.everything, .mentions].map { scope in createViewController(for: scope) } diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 9626bcaa66..25fb4e33f1 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -72,6 +72,8 @@ extension NotificationTimelineViewModel.Scope { return L10n.Scene.Notification.Title.everything case .mentions: return L10n.Scene.Notification.Title.mentions + case .fromAccount(let account): + return "Notifications from \(account.displayName)" } } } diff --git a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift index beaa8d56b6..ff7be6c43a 100644 --- a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift +++ b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift @@ -202,7 +202,9 @@ private extension FeedDataController { return try await getFeeds(with: .everything) case .notificationMentions: return try await getFeeds(with: .mentions) - + case .notificationAccount: + //TODO: Implement, inject account + return [] } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index bb944ac7a6..fec0fe9f86 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -10,36 +10,14 @@ import CoreData import CoreDataStack import Foundation import MastodonSDK -import OSLog extension APIService { public enum MastodonNotificationScope: String, Hashable, CaseIterable { case everything case mentions - - public var includeTypes: [MastodonNotificationType]? { - switch self { - case .everything: return nil - case .mentions: return [.mention, .status] - } - } - - public var excludeTypes: [MastodonNotificationType]? { - switch self { - case .everything: return nil - case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll] - } - } - - public var _excludeTypes: [Mastodon.Entity.Notification.NotificationType]? { - switch self { - case .everything: return nil - case .mentions: return [.follow, .followRequest, .reblog, .favourite, .poll] - } - } } - + public func notifications( maxID: Mastodon.Entity.Status.ID?, scope: MastodonNotificationScope, diff --git a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift index 33208fcc66..5e0ad66562 100644 --- a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift +++ b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift @@ -10,6 +10,7 @@ public final class MastodonFeed { case home(timeline: TimelineContext) case notificationAll case notificationMentions + case notificationAccount public enum TimelineContext: Equatable { case home From b424dab5b593dd8fda729e82e572331a0320ec94 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 Jul 2024 17:32:21 +0200 Subject: [PATCH 22/48] Fix build issues that happened after rebase (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 73 ++++++++++++++---------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 49029612ad..cf8c26b639 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -137,7 +137,6 @@ D8099078294BC8A30050219F /* PrivacyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8099077294BC8A30050219F /* PrivacyTableViewController.swift */; }; D809907A294BC9390050219F /* PrivacyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8099079294BC9390050219F /* PrivacyTableViewCell.swift */; }; D809907C294D25510050219F /* PrivacyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D809907B294D25510050219F /* PrivacyViewModel.swift */; }; - D80EC2632C2978EE009724A5 /* NotificationPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */; }; D80F627C2B5C32C500877059 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80F627A2B5C32C500877059 /* NotificationView.swift */; }; D81439862AD415DE0071A88F /* AboutInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81439852AD415DE0071A88F /* AboutInstance.swift */; }; D81439882AD450A40071A88F /* AboutInstanceTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81439872AD450A40071A88F /* AboutInstanceTableViewDataSource.swift */; }; @@ -160,14 +159,15 @@ D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; }; D83B54F82C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */; }; D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; }; - D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */; }; - D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */; }; - D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */; }; - D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */; }; - D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; + D85DF96B2C481AF700A01408 /* NotificationPolicyFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */; }; + D85DF96C2C481AF700A01408 /* NotificationPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */; }; + D85DF96D2C481AF700A01408 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */; }; + D85DF9712C481B1100A01408 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */; }; + D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */; }; + D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */; }; D87364F92AE28DB500C8F919 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = D87364F82AE28DB500C8F919 /* Kanna */; }; D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; @@ -768,7 +768,6 @@ D8099077294BC8A30050219F /* PrivacyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyTableViewController.swift; sourceTree = ""; }; D8099079294BC9390050219F /* PrivacyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyTableViewCell.swift; sourceTree = ""; }; D809907B294D25510050219F /* PrivacyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyViewModel.swift; sourceTree = ""; }; - D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyViewController.swift; sourceTree = ""; }; D80F627A2B5C32C500877059 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; D81439852AD415DE0071A88F /* AboutInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstance.swift; sourceTree = ""; }; D81439872AD450A40071A88F /* AboutInstanceTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceTableViewDataSource.swift; sourceTree = ""; }; @@ -799,11 +798,6 @@ D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = ""; tabWidth = 4; }; D83B54F72C2AC2FA00D18A7B /* NotificationFilteringBannerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringBannerTableViewCell.swift; sourceTree = ""; }; D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; }; - D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; }; - D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; - D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; - D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyHeaderView.swift; sourceTree = ""; }; - D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -813,6 +807,12 @@ D84C09A42B0F9E41009E685E /* Acknowledgments.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Acknowledgments.md; sourceTree = ""; }; D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceViewController.swift; sourceTree = ""; }; D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceRulesViewController.swift; sourceTree = ""; }; + D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyFilterTableViewCell.swift; path = Policy/NotificationPolicyFilterTableViewCell.swift; sourceTree = ""; }; + D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyViewController.swift; path = Policy/NotificationPolicyViewController.swift; sourceTree = ""; }; + D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPolicyHeaderView.swift; path = Policy/NotificationPolicyHeaderView.swift; sourceTree = ""; }; + D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; + D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; + D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; @@ -1776,8 +1776,9 @@ D80EC2602C2978CB009724A5 /* Notification Filtering */ = { isa = PBXGroup; children = ( - D84BB7732C3EB7F600493718 /* Requests */, - D84BB7702C3E565100493718 /* Policy */, + D85DF9682C481AF400A01408 /* NotificationPolicyFilterTableViewCell.swift */, + D85DF96A2C481AF700A01408 /* NotificationPolicyHeaderView.swift */, + D85DF9692C481AF700A01408 /* NotificationPolicyViewController.swift */, ); path = "Notification Filtering"; sourceTree = ""; @@ -1831,25 +1832,6 @@ path = Shared; sourceTree = ""; }; - D84BB7702C3E565100493718 /* Policy */ = { - isa = PBXGroup; - children = ( - D80EC2622C2978EE009724A5 /* NotificationPolicyViewController.swift */, - D84BB76A2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift */, - D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */, - ); - path = Policy; - sourceTree = ""; - }; - D84BB7732C3EB7F600493718 /* Requests */ = { - isa = PBXGroup; - children = ( - D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, - D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */, - ); - path = Requests; - sourceTree = ""; - }; D84C099E2B0F9E41009E685E /* Documentation */ = { isa = PBXGroup; children = ( @@ -1863,6 +1845,16 @@ path = Documentation; sourceTree = ""; }; + D85DF9702C481B1100A01408 /* Requests */ = { + isa = PBXGroup; + children = ( + D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */, + D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */, + ); + name = Requests; + path = "Notification Filtering/Requests"; + sourceTree = ""; + }; D8A6AB68291C50F3003AB663 /* Login */ = { isa = PBXGroup; children = ( @@ -2440,6 +2432,7 @@ DB697DDC278F521D004EF2F7 /* DataSourceFacade.swift */, 6213AF5D2893A8B200BCADB6 /* DataSourceFacade+Bookmark.swift */, DB697DE0278F5296004EF2F7 /* DataSourceFacade+Model.swift */, + D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */, DB697DDE278F524F004EF2F7 /* DataSourceFacade+Profile.swift */, DB8F7075279E954700E1225B /* DataSourceFacade+Follow.swift */, DB603110279EB38500A935FE /* DataSourceFacade+Mute.swift */, @@ -2451,7 +2444,6 @@ DB0FCB67279507EF006C02E2 /* DataSourceFacade+Meta.swift */, 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */, DB0FCB79279576A2006C02E2 /* DataSourceFacade+Thread.swift */, - D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */, DB63F74627990B0600455B82 /* DataSourceFacade+Hashtag.swift */, DB63F7532799491600455B82 /* DataSourceFacade+SearchHistory.swift */, DB159C2A27A17BAC0068DC77 /* DataSourceFacade+Media.swift */, @@ -2711,6 +2703,7 @@ DB9D6BFD25E4F57B0051B173 /* Notification */ = { isa = PBXGroup; children = ( + D85DF9702C481B1100A01408 /* Requests */, D80EC2602C2978CB009724A5 /* Notification Filtering */, DB63F765279A5E5600455B82 /* NotificationTimeline */, 2D35237F26256F470031AF25 /* Cell */, @@ -3542,7 +3535,7 @@ DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */, DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, - D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */, + D85DF9712C481B1100A01408 /* NotificationRequestsTableViewController.swift in Sources */, D8FAAE432AD047B200DC1832 /* AboutInstanceTableFooterView.swift in Sources */, D808B94E296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift in Sources */, D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */, @@ -3581,6 +3574,7 @@ DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, DB5B54A62833BE0000DEF8B2 /* UserListViewModel+State.swift in Sources */, DB0617ED277F02C50030EE79 /* OnboardingNavigationController.swift in Sources */, + D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */, D808B94C296ECFDC0031EB1E /* StatusEditHistoryViewModel.swift in Sources */, DB0617F527855AB90030EE79 /* ServerRuleSection.swift in Sources */, DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */, @@ -3591,7 +3585,6 @@ DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */, 2D24E1232626ED9D00A59D4F /* UIView+Gesture.swift in Sources */, DBFEEC9D279C12C1004F81DD /* ProfileFieldEditCollectionViewCell.swift in Sources */, - D84BB76B2C3BE33800493718 /* NotificationPolicyFilterTableViewCell.swift in Sources */, DB3E6FEC2806D7F100B035AE /* DiscoveryNewsViewController.swift in Sources */, DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */, D8FAAE412AD0475900DC1832 /* AboutInstanceTableViewHeader.swift in Sources */, @@ -3628,10 +3621,10 @@ 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, + D85DF96C2C481AF700A01408 /* NotificationPolicyViewController.swift in Sources */, D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */, DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */, - D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */, DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */, @@ -3649,6 +3642,7 @@ 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */, DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */, D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */, + D85DF96B2C481AF700A01408 /* NotificationPolicyFilterTableViewCell.swift in Sources */, DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */, D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */, 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */, @@ -3801,8 +3795,8 @@ DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, DB0FCB822796AC78006C02E2 /* UserTimelineViewController+DataSourceProvider.swift in Sources */, DB0EF72E26FDB24F00347686 /* SidebarListContentView.swift in Sources */, + D85DF96D2C481AF700A01408 /* NotificationPolicyHeaderView.swift in Sources */, DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */, - D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */, 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, @@ -3858,7 +3852,6 @@ DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */, D8318A802A4466D300C0FB73 /* SettingsCoordinator.swift in Sources */, DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */, - D80EC2632C2978EE009724A5 /* NotificationPolicyViewController.swift in Sources */, DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */, DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */, DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */, @@ -3899,13 +3892,13 @@ DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */, 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */, DB0FCB882796BDA9006C02E2 /* SearchItem.swift in Sources */, - D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */, DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */, 2A409F832B5955290044E472 /* MastodonStatusThreadViewModel+State.swift in Sources */, 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */, DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */, 5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */, + D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From fb3dfb5a6a1c1b476c61c178422b7b4e24e4b755 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 Jul 2024 18:04:22 +0200 Subject: [PATCH 23/48] Show notifications from one account (IOS-241) --- Mastodon/Coordinator/SceneCoordinator.swift | 4 +- .../DataSourceFacade+Notifications.swift | 3 +- ...ionTimelineViewModel+LoadOldestState.swift | 21 ++++++-- .../NotificationTimelineViewModel.swift | 19 +++----- .../DataController/FeedDataController.swift | 9 ++-- .../Service/API/APIService+Notification.swift | 48 ++++++++----------- .../Sources/MastodonSDK/MastodonFeed.swift | 2 +- 7 files changed, 55 insertions(+), 51 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 7bfd4208bd..54b63c4dfa 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -209,7 +209,7 @@ extension SceneCoordinator { // Notifications case notificationPolicy(viewModel: NotificationFilterViewModel) case notificationRequests(viewModel: NotificationRequestsViewModel) - case notificationtimeline(viewModel: NotificationTimelineViewModel) + case notificationTimeline(viewModel: NotificationTimelineViewModel) // report case report(viewModel: ReportViewModel) @@ -568,7 +568,7 @@ private extension SceneCoordinator { viewController = NotificationRequestsTableViewController(viewModel: viewModel, appContext: appContext, coordinator: self) case .notificationPolicy(let viewModel): viewController = NotificationPolicyViewController(viewModel: viewModel) - case .notificationtimeline(let viewModel): + case .notificationTimeline(let viewModel): viewController = NotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self) } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index cb2d748e24..4718fc946b 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -32,11 +32,12 @@ extension DataSourceFacade { provider.coordinator.showLoading() do { - // load notifications for request.account // show NotificationTimelineViewController with NotificationTimelineViewModel + let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authContext: provider.authContext, scope: .fromAccount(request.account)) provider.coordinator.hideLoading() + provider.coordinator.present(scene: .notificationTimeline(viewModel: notificationTimelineViewModel), transition: .show) } catch { //TODO: Error Handling provider.coordinator.hideLoading() diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift index 6c9ea12103..7c6d8a347e 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift @@ -9,6 +9,7 @@ import CoreDataStack import Foundation import GameplayKit import MastodonSDK +import MastodonCore extension NotificationTimelineViewModel { class LoadOldestState: GKState { @@ -51,8 +52,22 @@ extension NotificationTimelineViewModel.LoadOldestState { stateMachine.enter(Fail.self) return } - let scope = viewModel.scope + let scope: APIService.MastodonNotificationScope? + let accountID: String? + + switch viewModel.scope { + case .everything: + scope = .everything + accountID = nil + case .mentions: + scope = .mentions + accountID = nil + case .fromAccount(let account): + scope = nil + accountID = account.id + } + Task { let _maxID: Mastodon.Entity.Notification.ID? = lastFeedRecord.notification?.id @@ -64,8 +79,8 @@ extension NotificationTimelineViewModel.LoadOldestState { do { let response = try await viewModel.context.apiService.notifications( maxID: maxID, - //FIXME: Use correct scope for accounts - scope: .everything, + accountID: accountID, + scope: scope, authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index c42bbe6be5..653c597a8d 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -48,7 +48,7 @@ final class NotificationTimelineViewModel { context: AppContext, authContext: AuthContext, scope: Scope, - notificationPolicy: Mastodon.Entity.NotificationPolicy? + notificationPolicy: Mastodon.Entity.NotificationPolicy? = nil ) { self.context = context self.authContext = authContext @@ -65,8 +65,7 @@ final class NotificationTimelineViewModel { self.dataController.records = (try? FileManager.default.cachedNotificationsMentions(for: authContext.mastodonAuthenticationBox))?.map({ notification in MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions) }) ?? [] - case .fromAccount(let account): - //TODO: Implement + case .fromAccount(_): self.dataController.records = [] } @@ -84,14 +83,12 @@ final class NotificationTimelineViewModel { case .mentions: FileManager.default.cacheNotificationsMentions(items: items, for: authContext.mastodonAuthenticationBox) case .fromAccount(_): - //TODO: Implement + //TODO: we don't persist these break } }) .store(in: &disposeBag) } - - } extension NotificationTimelineViewModel { @@ -114,9 +111,8 @@ extension NotificationTimelineViewModel { dataController.loadInitial(kind: .notificationAll) case .mentions: dataController.loadInitial(kind: .notificationMentions) - case .fromAccount(_): - //TODO: Implement - break + case .fromAccount(let account): + dataController.loadInitial(kind: .notificationAccount(account.id)) } didLoadLatest.send() @@ -129,9 +125,8 @@ extension NotificationTimelineViewModel { dataController.loadNext(kind: .notificationAll) case .mentions: dataController.loadNext(kind: .notificationMentions) - case .fromAccount(_): - //TODO: Implement - break + case .fromAccount(let account): + dataController.loadNext(kind: .notificationAccount(account.id)) } } } diff --git a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift index ff7be6c43a..d91a98c28f 100644 --- a/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift +++ b/MastodonSDK/Sources/MastodonCore/DataController/FeedDataController.swift @@ -202,15 +202,14 @@ private extension FeedDataController { return try await getFeeds(with: .everything) case .notificationMentions: return try await getFeeds(with: .mentions) - case .notificationAccount: - //TODO: Implement, inject account - return [] + case .notificationAccount(let accountID): + return try await getFeeds(with: nil, accountID: accountID) } } - private func getFeeds(with scope: APIService.MastodonNotificationScope) async throws -> [MastodonFeed] { + private func getFeeds(with scope: APIService.MastodonNotificationScope?, accountID: String? = nil) async throws -> [MastodonFeed] { - let notifications = try await context.apiService.notifications(maxID: nil, scope: scope, authenticationBox: authContext.mastodonAuthenticationBox).value + let notifications = try await context.apiService.notifications(maxID: nil, accountID: accountID, scope: scope, authenticationBox: authContext.mastodonAuthenticationBox).value let accounts = notifications.map { $0.account } let relationships = try await context.apiService.relationship(forAccounts: accounts, authenticationBox: authContext.mastodonAuthenticationBox).value diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index fec0fe9f86..2497a64ea2 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -20,38 +20,32 @@ extension APIService { public func notifications( maxID: Mastodon.Entity.Status.ID?, - scope: MastodonNotificationScope, + accountID: String? = nil, + scope: MastodonNotificationScope?, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Notification]> { let authorization = authenticationBox.userAuthorization - + + let types: [Mastodon.Entity.Notification.NotificationType]? + let excludedTypes: [Mastodon.Entity.Notification.NotificationType]? + + switch scope { + case .everything: + types = [.follow, .followRequest, .mention, .reblog, .favourite, .poll, .status, .moderationWarning] + excludedTypes = nil + case .mentions: + types = [.mention] + excludedTypes = [.follow, .followRequest, .reblog, .favourite, .poll] + case nil: + types = nil + excludedTypes = nil + } + let query = Mastodon.API.Notifications.Query( maxID: maxID, - types: { - switch scope { - case .everything: - return [ - .follow, - .followRequest, - .mention, - .reblog, - .favourite, - .poll, - .status, - .moderationWarning - ] - case .mentions: - return [.mention] - } - }(), - excludeTypes: { - switch scope { - case .everything: - return nil - case .mentions: - return [.follow, .followRequest, .reblog, .favourite, .poll] - } - }() + types: types, + excludeTypes: excludedTypes, + accountID: accountID ) let response = try await Mastodon.API.Notifications.getNotifications( diff --git a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift index 5e0ad66562..df53767b38 100644 --- a/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift +++ b/MastodonSDK/Sources/MastodonSDK/MastodonFeed.swift @@ -10,7 +10,7 @@ public final class MastodonFeed { case home(timeline: TimelineContext) case notificationAll case notificationMentions - case notificationAccount + case notificationAccount(String) public enum TimelineContext: Equatable { case home From 8115aea0dbd2cd57e9be5b364f19a2397c06a4c6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 Jul 2024 18:35:28 +0200 Subject: [PATCH 24/48] Some more UI-feedback (IOS-241) --- .../Policy/NotificationPolicyHeaderView.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift index 2d6e2fa776..8dbf081d6c 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift @@ -15,11 +15,13 @@ class NotificationPolicyHeaderView: UIView { titleLabel.text = "Filter Notifications from..." - let buttonImageConfiguration = UIImage.SymbolConfiguration(scale: .large) + let buttonImageConfiguration = UIImage + .SymbolConfiguration(pointSize: 30) + .applying(UIImage.SymbolConfiguration(paletteColors: [.secondaryLabel, .quaternarySystemFill])) let buttonImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: buttonImageConfiguration) var buttonConfiguration = UIButton.Configuration.plain() buttonConfiguration.image = buttonImage - buttonConfiguration.contentInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10) + buttonConfiguration.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 0) closeButton = UIButton(configuration: buttonConfiguration) closeButton.translatesAutoresizingMaskIntoConstraints = false @@ -37,7 +39,7 @@ class NotificationPolicyHeaderView: UIView { private func setupConstraints() { let constraints = [ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), - titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20), + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10), closeButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 8), bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor), From 1a123d04be284221b24ce09f9271cc12e09ea34a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 10:38:16 +0200 Subject: [PATCH 25/48] Set title and fix background color (IOS-241) --- .../NotificationTimelineViewController.swift | 5 ++++- .../NotificationTimelineViewModel.swift | 12 ++++++++++++ .../Scene/Notification/NotificationViewModel.swift | 13 ------------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index bdb00cb6db..5a8f77349f 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -31,7 +31,7 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc private(set) lazy var tableView: UITableView = { let tableView = UITableView() - tableView.backgroundColor = .clear + tableView.backgroundColor = .secondarySystemBackground tableView.rowHeight = UITableView.automaticDimension tableView.separatorStyle = .none return tableView @@ -45,6 +45,9 @@ final class NotificationTimelineViewController: UIViewController, NeedsDependenc self.coordinator = coordinator super.init(nibName: nil, bundle: nil) + + title = viewModel.scope.title + view.backgroundColor = .secondarySystemBackground } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 653c597a8d..639a20d6f5 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -11,6 +11,7 @@ import CoreDataStack import GameplayKit import MastodonSDK import MastodonCore +import MastodonLocalization final class NotificationTimelineViewModel { @@ -96,6 +97,17 @@ extension NotificationTimelineViewModel { case everything case mentions case fromAccount(Mastodon.Entity.Account) + + var title: String { + switch self { + case .everything: + return L10n.Scene.Notification.Title.everything + case .mentions: + return L10n.Scene.Notification.Title.mentions + case .fromAccount(let account): + return "Notifications from \(account.displayName)" + } + } } } diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 25fb4e33f1..8b68367c19 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -64,19 +64,6 @@ final class NotificationViewModel { } } } - -extension NotificationTimelineViewModel.Scope { - var title: String { - switch self { - case .everything: - return L10n.Scene.Notification.Title.everything - case .mentions: - return L10n.Scene.Notification.Title.mentions - case .fromAccount(let account): - return "Notifications from \(account.displayName)" - } - } -} // MARK: - PageboyViewControllerDataSource extension NotificationViewModel: PageboyViewControllerDataSource { From e645bc1fd11aa6212d18a186420c5032fe6c9742 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 12:27:51 +0200 Subject: [PATCH 26/48] Show accept/reject-button (IOS-241) For now they are just ... there to gather feedback, functions follows --- .../NotificationRequestTableViewCell.swift | 85 ++++++++++++++++++- ...ificationRequestsTableViewController.swift | 5 +- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 4b56751f9c..6c6e504194 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -6,6 +6,13 @@ import MetaTextKit import MastodonMeta import MastodonUI import MastodonCore +import MastodonLocalization +import MastodonAsset + +protocol NotificationRequestTableViewCellDelegate: AnyObject { + // reject + // accept +} class NotificationRequestTableViewCell: UITableViewCell { static let reuseIdentifier = "NotificationRequestTableViewCell" @@ -15,11 +22,22 @@ class NotificationRequestTableViewCell: UITableViewCell { let avatarButton: AvatarButton private let labelStackView: UIStackView + private let avatarStackView: UIStackView private let contentStackView: UIStackView // private let stack // accept/deny-button + let acceptNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer() + let acceptNotificationRequestButton: HighlightDimmableButton + let acceptNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + + let rejectNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer() + let rejectNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + let rejectNotificationRequestButton: HighlightDimmableButton + + private let buttonStackView: UIStackView + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { nameLabel = MetaLabel(style: .statusName) usernameLabel = MetaLabel(style: .statusUsername) @@ -33,14 +51,64 @@ class NotificationRequestTableViewCell: UITableViewCell { labelStackView.alignment = .leading labelStackView.spacing = 4 - contentStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView]) + acceptNotificationRequestButton = HighlightDimmableButton() + acceptNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false + acceptNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) + acceptNotificationRequestButton.setTitleColor(.white, for: .normal) + acceptNotificationRequestButton.setTitle(L10n.Common.Controls.Actions.confirm, for: .normal) + acceptNotificationRequestButton.setImage(Asset.Editing.checkmark20.image.withRenderingMode(.alwaysTemplate), for: .normal) + acceptNotificationRequestButton.imageView?.contentMode = .scaleAspectFit + acceptNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.confirmFollowRequestButtonBackground.color), for: .normal) + acceptNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8) + acceptNotificationRequestButton.tintColor = .white + acceptNotificationRequestButton.layer.masksToBounds = true + acceptNotificationRequestButton.layer.cornerCurve = .continuous + acceptNotificationRequestButton.layer.cornerRadius = 10 + acceptNotificationRequestButton.accessibilityLabel = L10n.Scene.Notification.FollowRequest.accept + acceptNotificationRequestButtonShadowBackgroundContainer.cornerRadius = 10 + acceptNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1 + acceptNotificationRequestButtonShadowBackgroundContainer.addSubview(acceptNotificationRequestButton) + + rejectNotificationRequestButton = HighlightDimmableButton() + rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false + rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) + rejectNotificationRequestButton.setTitleColor(.black, for: .normal) + rejectNotificationRequestButton.setTitle(L10n.Common.Controls.Actions.delete, for: .normal) + rejectNotificationRequestButton.setImage(Asset.Circles.forbidden20.image.withRenderingMode(.alwaysTemplate), for: .normal) + rejectNotificationRequestButton.imageView?.contentMode = .scaleAspectFit + rejectNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8) + rejectNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.deleteFollowRequestButtonBackground.color), for: .normal) + rejectNotificationRequestButton.tintColor = .black + rejectNotificationRequestButton.layer.masksToBounds = true + rejectNotificationRequestButton.layer.cornerCurve = .continuous + rejectNotificationRequestButton.layer.cornerRadius = 10 + rejectNotificationRequestButton.accessibilityLabel = L10n.Scene.Notification.FollowRequest.reject + rejectNotificationRequestButtonShadowBackgroundContainer.cornerRadius = 10 + rejectNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1 + rejectNotificationRequestButtonShadowBackgroundContainer.addSubview(rejectNotificationRequestButton) + + buttonStackView = UIStackView(arrangedSubviews: [acceptNotificationRequestButtonShadowBackgroundContainer, rejectNotificationRequestButtonShadowBackgroundContainer]) + buttonStackView.axis = .horizontal + buttonStackView.distribution = .fillEqually + buttonStackView.spacing = 16 + buttonStackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0) // set bottom padding + + avatarStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView]) + avatarStackView.axis = .horizontal + avatarStackView.alignment = .center + avatarStackView.spacing = 12 + + contentStackView = UIStackView(arrangedSubviews: [avatarStackView, buttonStackView]) contentStackView.translatesAutoresizingMaskIntoConstraints = false - contentStackView.axis = .horizontal - contentStackView.alignment = .center - contentStackView.spacing = 12 + contentStackView.spacing = 16 + contentStackView.axis = .vertical + contentStackView.alignment = .leading super.init(style: style, reuseIdentifier: reuseIdentifier) +// acceptNotificationRequestButton.addTarget(self, action: #selector(NotificationView.acceptNotificationRequestButtonDidPressed(_:)), for: .touchUpInside) +// rejectNotificationRequestButton.addTarget(self, action: #selector(NotificationView.rejectNotificationRequestButtonDidPressed(_:)), for: .touchUpInside) + contentView.addSubview(contentStackView) setupConstraints() } @@ -55,11 +123,16 @@ class NotificationRequestTableViewCell: UITableViewCell { contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16), contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + buttonStackView.widthAnchor.constraint(equalTo: contentStackView.widthAnchor), + avatarStackView.widthAnchor.constraint(equalTo: contentStackView.widthAnchor), + avatarButton.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width).priority(.required - 1), avatarButton.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.required - 1), ] NSLayoutConstraint.activate(constraints) + acceptNotificationRequestButton.pinToParent() + rejectNotificationRequestButton.pinToParent() } override func prepareForReuse() { @@ -87,4 +160,8 @@ class NotificationRequestTableViewCell: UITableViewCell { let metaUsername = PlaintextMetaContent(string: "@\(account.acct)") usernameLabel.configure(content: metaUsername) } + + // MARK: - Actions + // reject + // accept } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 0b0a80491a..15a9912ffa 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -35,7 +35,7 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency tableView = UITableView(frame: .zero) tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.backgroundColor = .systemBackground + tableView.backgroundColor = .secondarySystemBackground tableView.register(NotificationRequestTableViewCell.self, forCellReuseIdentifier: NotificationRequestTableViewCell.reuseIdentifier) super.init(nibName: nil, bundle: nil) @@ -57,6 +57,9 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency tableView.dataSource = dataSource tableView.delegate = self self.dataSource = dataSource + + //TODO: Localization + title = "Filtered Notifications" } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } From 94a8791c4b8383769805dd33b2dc35441473a64e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 13:39:03 +0200 Subject: [PATCH 27/48] [WIP]: Add spinner and do something when pressing the button (IOS-241) --- .../NotificationRequestTableViewCell.swift | 53 +++++++++++++++---- ...ificationRequestsTableViewController.swift | 27 +++++++++- .../NotificationView/NotificationView.swift | 2 +- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 6c6e504194..3203792157 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -10,13 +10,16 @@ import MastodonLocalization import MastodonAsset protocol NotificationRequestTableViewCellDelegate: AnyObject { - // reject - // accept + func acceptNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: Mastodon.Entity.NotificationRequest) + func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: Mastodon.Entity.NotificationRequest) } class NotificationRequestTableViewCell: UITableViewCell { static let reuseIdentifier = "NotificationRequestTableViewCell" + var notificationRequest: Mastodon.Entity.NotificationRequest? + weak var delegate: NotificationRequestTableViewCellDelegate? + let nameLabel: MetaLabel let usernameLabel: MetaLabel let avatarButton: AvatarButton @@ -25,15 +28,12 @@ class NotificationRequestTableViewCell: UITableViewCell { private let avatarStackView: UIStackView private let contentStackView: UIStackView -// private let stack - // accept/deny-button - let acceptNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer() let acceptNotificationRequestButton: HighlightDimmableButton - let acceptNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + let acceptNotificationRequestActivityIndicatorView: UIActivityIndicatorView let rejectNotificationRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer() - let rejectNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + let rejectNotificationRequestActivityIndicatorView: UIActivityIndicatorView let rejectNotificationRequestButton: HighlightDimmableButton private let buttonStackView: UIStackView @@ -69,6 +69,13 @@ class NotificationRequestTableViewCell: UITableViewCell { acceptNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1 acceptNotificationRequestButtonShadowBackgroundContainer.addSubview(acceptNotificationRequestButton) + acceptNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + acceptNotificationRequestActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false + acceptNotificationRequestActivityIndicatorView.color = .black + acceptNotificationRequestActivityIndicatorView.hidesWhenStopped = true + acceptNotificationRequestActivityIndicatorView.stopAnimating() + acceptNotificationRequestButton.addSubview(acceptNotificationRequestActivityIndicatorView) + rejectNotificationRequestButton = HighlightDimmableButton() rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) @@ -87,6 +94,13 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1 rejectNotificationRequestButtonShadowBackgroundContainer.addSubview(rejectNotificationRequestButton) + rejectNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) + rejectNotificationRequestActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false + rejectNotificationRequestActivityIndicatorView.color = .black + rejectNotificationRequestActivityIndicatorView.hidesWhenStopped = true + rejectNotificationRequestActivityIndicatorView.stopAnimating() + rejectNotificationRequestButton.addSubview(rejectNotificationRequestActivityIndicatorView) + buttonStackView = UIStackView(arrangedSubviews: [acceptNotificationRequestButtonShadowBackgroundContainer, rejectNotificationRequestButtonShadowBackgroundContainer]) buttonStackView.axis = .horizontal buttonStackView.distribution = .fillEqually @@ -106,8 +120,8 @@ class NotificationRequestTableViewCell: UITableViewCell { super.init(style: style, reuseIdentifier: reuseIdentifier) -// acceptNotificationRequestButton.addTarget(self, action: #selector(NotificationView.acceptNotificationRequestButtonDidPressed(_:)), for: .touchUpInside) -// rejectNotificationRequestButton.addTarget(self, action: #selector(NotificationView.rejectNotificationRequestButtonDidPressed(_:)), for: .touchUpInside) + acceptNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.acceptNotificationRequest(_:)), for: .touchUpInside) + rejectNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.rejectNotificationRequest(_:)), for: .touchUpInside) contentView.addSubview(contentStackView) setupConstraints() @@ -128,6 +142,12 @@ class NotificationRequestTableViewCell: UITableViewCell { avatarButton.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width).priority(.required - 1), avatarButton.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.required - 1), + + acceptNotificationRequestActivityIndicatorView.centerXAnchor.constraint(equalTo: acceptNotificationRequestButton.centerXAnchor), + acceptNotificationRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: acceptNotificationRequestButton.centerYAnchor), + rejectNotificationRequestActivityIndicatorView.centerXAnchor.constraint(equalTo: rejectNotificationRequestButton.centerXAnchor), + rejectNotificationRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: rejectNotificationRequestButton.centerYAnchor), + ] NSLayoutConstraint.activate(constraints) @@ -159,9 +179,20 @@ class NotificationRequestTableViewCell: UITableViewCell { let metaUsername = PlaintextMetaContent(string: "@\(account.acct)") usernameLabel.configure(content: metaUsername) + + self.notificationRequest = request } // MARK: - Actions - // reject - // accept + @objc private func acceptNotificationRequest(_ sender: UIButton) { + guard let notificationRequest, let delegate else { return } + + delegate.acceptNotificationRequest(self, notificationRequest: notificationRequest) + } + @objc private func rejectNotificationRequest(_ sender: UIButton) { + guard let notificationRequest, let delegate else { return } + + delegate.rejectNotificationRequest(self, notificationRequest: notificationRequest) + } + } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 15a9912ffa..f0b7bfced5 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -28,7 +28,7 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency var dataSource: UITableViewDiffableDataSource? init(viewModel: NotificationRequestsViewModel, appContext: AppContext, coordinator: SceneCoordinator) { - //TODO: DataSource, Delegate.... + self.viewModel = viewModel self.context = appContext self.coordinator = coordinator @@ -50,6 +50,7 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency let request = viewModel.requests[indexPath.row] cell.configure(with: request) + cell.delegate = self return cell } @@ -91,3 +92,27 @@ extension NotificationRequestsTableViewController: UITableViewDelegate { extension NotificationRequestsTableViewController: AuthContextProvider { var authContext: AuthContext { viewModel.authContext } } + +extension NotificationRequestsTableViewController: NotificationRequestTableViewCellDelegate { + func acceptNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { + print("accept \(notificationRequest.id)") + cell.acceptNotificationRequestActivityIndicatorView.isHidden = false + cell.acceptNotificationRequestActivityIndicatorView.startAnimating() + + cell.acceptNotificationRequestButton.tintColor = .clear + cell.acceptNotificationRequestButton.setTitleColor(.clear, for: .normal) + + + //TODO: Send request, update cell, reload notification requests AND general notifications + } + + func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { + print("reject \(notificationRequest.id)") + + cell.rejectNotificationRequestActivityIndicatorView.isHidden = false + cell.rejectNotificationRequestActivityIndicatorView.startAnimating() + cell.rejectNotificationRequestButton.tintColor = .clear + cell.rejectNotificationRequestButton.setTitleColor(.clear, for: .normal) + + } +} diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift index 341dd6e725..cf330350b4 100644 --- a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift +++ b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift @@ -327,7 +327,7 @@ extension NotificationView { rejectFollowRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: rejectFollowRequestButton.centerYAnchor), ]) rejectFollowRequestActivityIndicatorView.color = .black - acceptFollowRequestActivityIndicatorView.hidesWhenStopped = true + rejectFollowRequestActivityIndicatorView.hidesWhenStopped = true rejectFollowRequestActivityIndicatorView.stopAnimating() // statusView From 8b65421103f072afef280c7e43b59272016cc1f8 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 13:42:17 +0200 Subject: [PATCH 28/48] [WIP] Add API-calls for accepting/rejecting notification requests (IOS-241) --- .../Service/API/APIService+Notification.swift | 16 +++++++ .../API/Mastodon+API+Notifications.swift | 48 +++++++++++++++++++ .../MastodonSDK/API/Mastodon+API.swift | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index 2497a64ea2..9a8a8b0cd7 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -125,4 +125,20 @@ extension APIService { return response } + + public func acceptNotificationRequests(authenticationBox: MastodonAuthenticationBox, id: String) async throws -> Mastodon.Response.Content<[String: String]> { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Notifications.acceptNotificationRequest(id: id, session: session, domain: domain, authorization: authorization) + return response + } + + public func rejectNotificationRequests(authenticationBox: MastodonAuthenticationBox, id: String) async throws -> Mastodon.Response.Content<[String: String]> { + let domain = authenticationBox.domain + let authorization = authenticationBox.userAuthorization + + let response = try await Mastodon.API.Notifications.dismissNotificationRequest(id: id, session: session, domain: domain, authorization: authorization) + return response + } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index d309e6cf5a..91f3271840 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -202,6 +202,18 @@ extension Mastodon.API.Notifications { notificationsEndpointURL(domain: domain).appendingPathComponent("requests") } + 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).appendingPathExtension("accept") + } + + internal static func dismissNotificationRequestEndpointURL(domain: String, id: String) -> URL { + notificationRequestEndpointURL(domain: domain, id: id).appendingPathExtension("dismiss") + } + public static func getNotificationRequests( session: URLSession, domain: String, @@ -217,4 +229,40 @@ extension Mastodon.API.Notifications { let value = try Mastodon.API.decode(type: [Mastodon.Entity.NotificationRequest].self, from: data, response: response) return Mastodon.Response.Content(value: value, response: response) } + + public static func acceptNotificationRequest( + id: String, + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization + ) async throws -> Mastodon.Response.Content<[String: String]> { + let request = Mastodon.API.post( + url: acceptNotificationRequestEndpointURL(domain: domain, id: id), + authorization: authorization + ) + + 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) + return Mastodon.Response.Content(value: value, response: response) + } + + public static func dismissNotificationRequest( + id: String, + session: URLSession, + domain: String, + authorization: Mastodon.API.OAuth.Authorization + ) async throws -> Mastodon.Response.Content<[String: String]> { + let request = Mastodon.API.post( + url: dismissNotificationRequestEndpointURL(domain: domain, id: id), + authorization: authorization + ) + + 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) + return Mastodon.Response.Content(value: value, response: response) + } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index 1103322879..a733a07051 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -149,7 +149,7 @@ extension Mastodon.API { static func post( url: URL, - query: PostQuery?, + query: PostQuery? = nil, authorization: OAuth.Authorization? = nil ) -> URLRequest { return buildRequest(url: url, method: .POST, query: query, authorization: authorization) From 55d6b281a6839b4702b6fe7ca8b89e9549dec116 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 16:12:15 +0200 Subject: [PATCH 29/48] Use correct URL :facepalm: (IOS-241) --- .../Sources/MastodonSDK/API/Mastodon+API+Notifications.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift index 91f3271840..7244c6e9c6 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Notifications.swift @@ -207,11 +207,11 @@ extension Mastodon.API.Notifications { } internal static func acceptNotificationRequestEndpointURL(domain: String, id: String) -> URL { - notificationRequestEndpointURL(domain: domain, id: id).appendingPathExtension("accept") + notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("accept") } internal static func dismissNotificationRequestEndpointURL(domain: String, id: String) -> URL { - notificationRequestEndpointURL(domain: domain, id: id).appendingPathExtension("dismiss") + notificationRequestEndpointURL(domain: domain, id: id).appendingPathComponent("dismiss") } public static func getNotificationRequests( From 5925d84dd975e415a6521a8cfd29ba47667e6e33 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 18 Jul 2024 16:12:36 +0200 Subject: [PATCH 30/48] Accept/reject notification requests (IOS-241) --- .../NotificationRequestTableViewCell.swift | 10 +-- ...ificationRequestsTableViewController.swift | 84 +++++++++++++++++-- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 3203792157..6172b387b5 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -55,8 +55,8 @@ class NotificationRequestTableViewCell: UITableViewCell { acceptNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false acceptNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) acceptNotificationRequestButton.setTitleColor(.white, for: .normal) - acceptNotificationRequestButton.setTitle(L10n.Common.Controls.Actions.confirm, for: .normal) - acceptNotificationRequestButton.setImage(Asset.Editing.checkmark20.image.withRenderingMode(.alwaysTemplate), for: .normal) + acceptNotificationRequestButton.setTitle("Accept", for: .normal) + acceptNotificationRequestButton.setImage(UIImage(systemName: "checkmark"), for: .normal) acceptNotificationRequestButton.imageView?.contentMode = .scaleAspectFit acceptNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.confirmFollowRequestButtonBackground.color), for: .normal) acceptNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8) @@ -71,7 +71,7 @@ class NotificationRequestTableViewCell: UITableViewCell { acceptNotificationRequestActivityIndicatorView = UIActivityIndicatorView(style: .medium) acceptNotificationRequestActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - acceptNotificationRequestActivityIndicatorView.color = .black + acceptNotificationRequestActivityIndicatorView.color = .white acceptNotificationRequestActivityIndicatorView.hidesWhenStopped = true acceptNotificationRequestActivityIndicatorView.stopAnimating() acceptNotificationRequestButton.addSubview(acceptNotificationRequestActivityIndicatorView) @@ -80,8 +80,8 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) rejectNotificationRequestButton.setTitleColor(.black, for: .normal) - rejectNotificationRequestButton.setTitle(L10n.Common.Controls.Actions.delete, for: .normal) - rejectNotificationRequestButton.setImage(Asset.Circles.forbidden20.image.withRenderingMode(.alwaysTemplate), for: .normal) + rejectNotificationRequestButton.setTitle("Dismiss", for: .normal) + rejectNotificationRequestButton.setImage(UIImage(systemName: "speaker.slash"), for: .normal) rejectNotificationRequestButton.imageView?.contentMode = .scaleAspectFit rejectNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8) rejectNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.deleteFollowRequestButtonBackground.color), for: .normal) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index f0b7bfced5..41f8213019 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -17,11 +17,14 @@ enum NotificationRequestItem: Hashable { case item(Mastodon.Entity.NotificationRequest) } -class NotificationRequestsTableViewController: UIViewController, NeedsDependency { +protocol NotificationRequestsTableViewControllerDelegate: AnyObject { + func notificationRequestsUpdated(_ viewController: NotificationRequestsTableViewController) +} +class NotificationRequestsTableViewController: UIViewController, NeedsDependency { var context: AppContext! var coordinator: SceneCoordinator! - + weak var delegate: NotificationRequestsTableViewControllerDelegate? let tableView: UITableView var viewModel: NotificationRequestsViewModel @@ -95,24 +98,91 @@ extension NotificationRequestsTableViewController: AuthContextProvider { extension NotificationRequestsTableViewController: NotificationRequestTableViewCellDelegate { func acceptNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { - print("accept \(notificationRequest.id)") + cell.acceptNotificationRequestActivityIndicatorView.isHidden = false cell.acceptNotificationRequestActivityIndicatorView.startAnimating() - cell.acceptNotificationRequestButton.tintColor = .clear cell.acceptNotificationRequestButton.setTitleColor(.clear, for: .normal) - + cell.rejectNotificationRequestButton.isUserInteractionEnabled = false + cell.acceptNotificationRequestButton.isUserInteractionEnabled = false //TODO: Send request, update cell, reload notification requests AND general notifications + Task { [weak self] in + guard let self else { return } + do { + _ = try await context.apiService.acceptNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, + id: notificationRequest.id) + + let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + + if requests.count > 0 { + + await MainActor.run { [weak self] in + guard let self else { return } + self.viewModel.requests = requests + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + self.dataSource?.apply(snapshot) + } + } else { + await MainActor.run { [weak self] in + _ = self?.navigationController?.popViewController(animated: true) + } + } + + } catch { + cell.acceptNotificationRequestActivityIndicatorView.stopAnimating() + cell.acceptNotificationRequestButton.tintColor = .white + cell.acceptNotificationRequestButton.setTitleColor(.white, for: .normal) + cell.rejectNotificationRequestButton.isUserInteractionEnabled = true + cell.acceptNotificationRequestButton.isUserInteractionEnabled = true + } + } } func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { - print("reject \(notificationRequest.id)") cell.rejectNotificationRequestActivityIndicatorView.isHidden = false cell.rejectNotificationRequestActivityIndicatorView.startAnimating() cell.rejectNotificationRequestButton.tintColor = .clear cell.rejectNotificationRequestButton.setTitleColor(.clear, for: .normal) - + cell.rejectNotificationRequestButton.isUserInteractionEnabled = false + cell.acceptNotificationRequestButton.isUserInteractionEnabled = false + + Task { [weak self] in + guard let self else { return } + do { + _ = try await context.apiService.rejectNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, + id: notificationRequest.id) + + let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + + if requests.count > 0 { + + await MainActor.run { [weak self] in + guard let self else { return } + self.viewModel.requests = requests + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + self.dataSource?.apply(snapshot) + } + } else { + await MainActor.run { [weak self] in + _ = self?.navigationController?.popViewController(animated: true) + } + } + + } catch { + cell.rejectNotificationRequestActivityIndicatorView.stopAnimating() + cell.rejectNotificationRequestButton.tintColor = .black + cell.rejectNotificationRequestButton.setTitleColor(.black, for: .normal) + cell.rejectNotificationRequestButton.isUserInteractionEnabled = true + cell.acceptNotificationRequestButton.isUserInteractionEnabled = true + } + } } } From 67e1e5e3b411753b12da3c68df8b5f8fbb5df317 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 19 Jul 2024 09:20:50 +0200 Subject: [PATCH 31/48] Minor cleanup (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 4 ++++ Mastodon/Coordinator/SceneCoordinator.swift | 3 +-- .../DataSourceFacade+Notifications.swift | 15 ++++---------- ...ificationRequestsTableViewController.swift | 11 +++------- .../NotificationRequestsViewModel.swift | 20 +++++++++++++++++++ 5 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index cf8c26b639..73c7486822 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -168,6 +168,7 @@ D85DF9712C481B1100A01408 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */; }; D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */; }; D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */; }; + D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */; }; D87364F92AE28DB500C8F919 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = D87364F82AE28DB500C8F919 /* Kanna */; }; D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; @@ -813,6 +814,7 @@ D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; + D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsViewModel.swift; sourceTree = ""; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; @@ -1850,6 +1852,7 @@ children = ( D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */, D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */, + D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */, ); name = Requests; path = "Notification Filtering/Requests"; @@ -3597,6 +3600,7 @@ DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */, DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */, DB938EE62623F50700E5B6C1 /* ThreadViewController.swift in Sources */, + D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */, DB6180F426391D110018D199 /* MediaPreviewImageView.swift in Sources */, DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */, 2D939AB525EDD8A90076FA61 /* String.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 54b63c4dfa..3f4f8626f4 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -564,8 +564,7 @@ private extension SceneCoordinator { let composeViewController = ComposeViewController(viewModel: viewModel) viewController = composeViewController case .notificationRequests(let viewModel): - //FIXME: Put either AuthContext or Everything in ViewModel - viewController = NotificationRequestsTableViewController(viewModel: viewModel, appContext: appContext, coordinator: self) + viewController = NotificationRequestsTableViewController(viewModel: viewModel) case .notificationPolicy(let viewModel): viewController = NotificationPolicyViewController(viewModel: viewModel) case .notificationTimeline(let viewModel): diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index 4718fc946b..15ef9f1e52 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -13,7 +13,7 @@ extension DataSourceFacade { do { let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox).value - let viewModel = NotificationRequestsViewModel(requests: notificationRequests, authContext: provider.authContext) + let viewModel = NotificationRequestsViewModel(appContext: provider.context, authContext: provider.authContext, coordinator: provider.coordinator, requests: notificationRequests) provider.coordinator.hideLoading() @@ -31,17 +31,10 @@ extension DataSourceFacade { ) async { provider.coordinator.showLoading() - do { - // load notifications for request.account - // show NotificationTimelineViewController with NotificationTimelineViewModel - let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authContext: provider.authContext, scope: .fromAccount(request.account)) + let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authContext: provider.authContext, scope: .fromAccount(request.account)) - provider.coordinator.hideLoading() - provider.coordinator.present(scene: .notificationTimeline(viewModel: notificationTimelineViewModel), transition: .show) - } catch { - //TODO: Error Handling - provider.coordinator.hideLoading() - } + provider.coordinator.hideLoading() + provider.coordinator.present(scene: .notificationTimeline(viewModel: notificationTimelineViewModel), transition: .show) } } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 41f8213019..275f5830dd 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -4,11 +4,6 @@ import UIKit import MastodonSDK import MastodonCore -struct NotificationRequestsViewModel { - var requests: [Mastodon.Entity.NotificationRequest] - let authContext: AuthContext -} - enum NotificationRequestsSection: Hashable { case main } @@ -30,11 +25,11 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency var viewModel: NotificationRequestsViewModel var dataSource: UITableViewDiffableDataSource? - init(viewModel: NotificationRequestsViewModel, appContext: AppContext, coordinator: SceneCoordinator) { + init(viewModel: NotificationRequestsViewModel) { self.viewModel = viewModel - self.context = appContext - self.coordinator = coordinator + self.context = viewModel.appContext + self.coordinator = viewModel.coordinator tableView = UITableView(frame: .zero) tableView.translatesAutoresizingMaskIntoConstraints = false diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift new file mode 100644 index 0000000000..4ffe3be116 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsViewModel.swift @@ -0,0 +1,20 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonSDK +import MastodonCore + +struct NotificationRequestsViewModel { + let appContext: AppContext + let authContext: AuthContext + let coordinator: SceneCoordinator + + var requests: [Mastodon.Entity.NotificationRequest] + + init(appContext: AppContext, authContext: AuthContext, coordinator: SceneCoordinator, requests: [Mastodon.Entity.NotificationRequest]) { + self.appContext = appContext + self.authContext = authContext + self.coordinator = coordinator + self.requests = requests + } +} From 48f58642ac98a2b7dac7678df873375fefeca44b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 Jul 2024 11:40:13 +0200 Subject: [PATCH 32/48] Add first draft for Notification-count-view (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 4 ++ .../NotificationRequestCountView.swift | 44 +++++++++++++++++++ .../NotificationRequestTableViewCell.swift | 13 ++++++ 3 files changed, 61 insertions(+) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 73c7486822..426914cb3f 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -169,6 +169,7 @@ D85DF9722C481B1100A01408 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */; }; D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */; }; D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */; }; + D85DF97A2C4E49A400A01408 /* NotificationRequestCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */; }; D87364F92AE28DB500C8F919 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = D87364F82AE28DB500C8F919 /* Kanna */; }; D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; @@ -815,6 +816,7 @@ D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsViewModel.swift; sourceTree = ""; }; + D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestCountView.swift; sourceTree = ""; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; @@ -1853,6 +1855,7 @@ D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */, D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */, D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */, + D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */, ); name = Requests; path = "Notification Filtering/Requests"; @@ -3825,6 +3828,7 @@ D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */, D8F917142A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift in Sources */, 2A5242772C199EC2005B9E22 /* PrivacySafetySettingPreset.swift in Sources */, + D85DF97A2C4E49A400A01408 /* NotificationRequestCountView.swift in Sources */, DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */, 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */, diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift new file mode 100644 index 0000000000..4fbac07339 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestCountView.swift @@ -0,0 +1,44 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonAsset + +class NotificationRequestCountView: UIView { + + let countLabel: UILabel + + init() { + countLabel = UILabel() + countLabel.translatesAutoresizingMaskIntoConstraints = false + countLabel.textColor = .white + countLabel.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .regular)) + countLabel.textAlignment = .center + + super.init(frame: .zero) + + addSubview(countLabel) + + backgroundColor = Asset.Colors.Brand.blurple.color + layer.borderWidth = 2.0 + layer.borderColor = UIColor.white.cgColor + applyCornerRadius(radius: 10) + + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + countLabel.topAnchor.constraint(equalTo: topAnchor, constant: 2), + countLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5), + trailingAnchor.constraint(equalTo: countLabel.trailingAnchor, constant: 5), + bottomAnchor.constraint(equalTo: countLabel.bottomAnchor, constant: 2), + + widthAnchor.constraint(greaterThanOrEqualToConstant: 20), + heightAnchor.constraint(greaterThanOrEqualToConstant: 20) + ] + + NSLayoutConstraint.activate(constraints) + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 6172b387b5..4d2a189590 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -36,6 +36,8 @@ class NotificationRequestTableViewCell: UITableViewCell { let rejectNotificationRequestActivityIndicatorView: UIActivityIndicatorView let rejectNotificationRequestButton: HighlightDimmableButton + let requestCountView: NotificationRequestCountView + private let buttonStackView: UIStackView override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -118,12 +120,16 @@ class NotificationRequestTableViewCell: UITableViewCell { contentStackView.axis = .vertical contentStackView.alignment = .leading + requestCountView = NotificationRequestCountView() + requestCountView.translatesAutoresizingMaskIntoConstraints = false + super.init(style: style, reuseIdentifier: reuseIdentifier) acceptNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.acceptNotificationRequest(_:)), for: .touchUpInside) rejectNotificationRequestButton.addTarget(self, action: #selector(NotificationRequestTableViewCell.rejectNotificationRequest(_:)), for: .touchUpInside) contentView.addSubview(contentStackView) + contentView.addSubview(requestCountView) setupConstraints() } @@ -148,6 +154,9 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestActivityIndicatorView.centerXAnchor.constraint(equalTo: rejectNotificationRequestButton.centerXAnchor), rejectNotificationRequestActivityIndicatorView.centerYAnchor.constraint(equalTo: rejectNotificationRequestButton.centerYAnchor), + requestCountView.trailingAnchor.constraint(equalTo: avatarButton.trailingAnchor, constant: 2), + requestCountView.bottomAnchor.constraint(equalTo: avatarButton.bottomAnchor, constant: 2), + ] NSLayoutConstraint.activate(constraints) @@ -180,6 +189,10 @@ class NotificationRequestTableViewCell: UITableViewCell { let metaUsername = PlaintextMetaContent(string: "@\(account.acct)") usernameLabel.configure(content: metaUsername) + requestCountView.countLabel.text = request.notificationsCount + requestCountView.setNeedsLayout() + requestCountView.layoutIfNeeded() + self.notificationRequest = request } From d87deffa6d68863e2ee98e1d5b089cbbdac1acc7 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 10:28:51 +0200 Subject: [PATCH 33/48] Update notifications after policy/request-change (IOS-241) --- .../Policy/NotificationPolicyViewController.swift | 2 +- .../NotificationRequestsTableViewController.swift | 5 ++++- .../NotificationTimelineViewModel.swift | 11 +++++++++++ .../Service/API/APIService+Notification.swift | 4 ++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift index b64fdb9d85..3cfde4b6c7 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift @@ -156,7 +156,7 @@ class NotificationPolicyViewController: UIViewController { filterPrivateMentions: viewModel.privateMentions ) - + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) } catch { //TODO: Error Handling } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 275f5830dd..c19c0a8a66 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -101,7 +101,6 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC cell.rejectNotificationRequestButton.isUserInteractionEnabled = false cell.acceptNotificationRequestButton.isUserInteractionEnabled = false - //TODO: Send request, update cell, reload notification requests AND general notifications Task { [weak self] in guard let self else { return } do { @@ -110,6 +109,8 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) + if requests.count > 0 { await MainActor.run { [weak self] in @@ -154,6 +155,8 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) + if requests.count > 0 { await MainActor.run { [weak self] in diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 639a20d6f5..a60f48025a 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -89,6 +89,17 @@ final class NotificationTimelineViewModel { } }) .store(in: &disposeBag) + + NotificationCenter.default.addObserver(self, selector: #selector(Self.notificationFilteringChanged(_:)), name: .notificationFilteringChanged, object: nil) + } + + //MARK: - Notifications + + @objc func notificationFilteringChanged(_ notification: Notification) { + dataController.records = [] + Task { [weak self] in + await self?.loadLatest() + } } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift index 9a8a8b0cd7..9af896130d 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Notification.swift @@ -142,3 +142,7 @@ extension APIService { return response } } + +extension Notification.Name { + public static let notificationFilteringChanged = Notification.Name(rawValue: "org.joinmastodon.app.notificationFilteringsChanged") +} From 25f15d256e460ede4ee850f2f0a8433c20eafccc Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 11:38:20 +0200 Subject: [PATCH 34/48] Update policies (IOS-241) --- .../Policy/NotificationPolicyViewController.swift | 13 +++++++++++-- .../Notification/NotificationViewController.swift | 14 +++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift index 3cfde4b6c7..0e35041865 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift @@ -4,6 +4,7 @@ import UIKit import MastodonLocalization import MastodonAsset import MastodonCore +import MastodonSDK enum NotificationFilterSection: Hashable { case main @@ -61,6 +62,10 @@ struct NotificationFilterViewModel { } } +protocol NotificationPolicyViewControllerDelegate: AnyObject { + func policyUpdated(_ viewController: NotificationPolicyViewController, newPolicy: Mastodon.Entity.NotificationPolicy) +} + class NotificationPolicyViewController: UIViewController { let tableView: UITableView @@ -69,6 +74,7 @@ class NotificationPolicyViewController: UIViewController { var dataSource: UITableViewDiffableDataSource? let items: [NotificationFilterItem] var viewModel: NotificationFilterViewModel + weak var delegate: NotificationPolicyViewControllerDelegate? init(viewModel: NotificationFilterViewModel) { self.viewModel = viewModel @@ -148,15 +154,18 @@ class NotificationPolicyViewController: UIViewController { guard let self else { return } do { - _ = try await viewModel.appContext.apiService.updateNotificationPolicy( + let updatedPolicy = try await viewModel.appContext.apiService.updateNotificationPolicy( authenticationBox: authenticationBox, filterNotFollowing: viewModel.notFollowing, filterNotFollowers: viewModel.noFollower, filterNewAccounts: viewModel.newAccount, filterPrivateMentions: viewModel.privateMentions - ) + ).value + + delegate?.policyUpdated(self, newPolicy: updatedPolicy) NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) + } catch { //TODO: Error Handling } diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index b2d943ad26..5f55e15301 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -12,6 +12,7 @@ import MastodonLocalization import Tabman import Pageboy import MastodonCore +import MastodonSDK final class NotificationViewController: TabmanViewController, NeedsDependency { @@ -128,7 +129,9 @@ extension NotificationViewController { privateMentions: policy.filterPrivateMentions ) - _ = coordinator.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) + guard let policyViewController = coordinator.present(scene: .notificationPolicy(viewModel: policyViewModel), transition: .formSheet) as? NotificationPolicyViewController else { return } + + policyViewController.delegate = self } } @@ -250,3 +253,12 @@ extension NotificationViewController { return categorySwitchKeyCommands } } + + +//MARK: - NotificationPolicyViewControllerDelegate + +extension NotificationViewController: NotificationPolicyViewControllerDelegate { + func policyUpdated(_ viewController: NotificationPolicyViewController, newPolicy: Mastodon.Entity.NotificationPolicy) { + viewModel?.notificationPolicy = newPolicy + } +} From 9e8de739ec665d9f0bdbbde2cfbce46cba1c88f1 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 12:05:34 +0200 Subject: [PATCH 35/48] Localization (IOS-241) --- .../input/Base.lproj/app.json | 29 ++++++++++++++++++- Localization/app.json | 19 ++++++++++++ .../Policy/NotificationPolicyHeaderView.swift | 4 +-- .../NotificationPolicyViewController.swift | 26 +++++++---------- .../Generated/Strings.swift | 28 ++++++++++++++++++ .../Resources/Base.lproj/Localizable.strings | 19 ++++++++---- 6 files changed, 101 insertions(+), 24 deletions(-) diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index f11b057a6a..a18a5358b9 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -477,7 +477,15 @@ "title": "Home", "timeline_menu": { "following": "Following", - "local_community": "Local" + "local_community": "Local", + "lists": { + "title": "Lists", + "empty_message": "You don't have any Lists" + }, + "hashtags": { + "title": "Followed Hashtags", + "empty_message": "You don't follow any Hashtags" + } }, "timeline_pill": { "offline": "Offline", @@ -744,6 +752,25 @@ "silence": "Your account has been limited.", "suspend": "Your account has been suspended.", "learn_more": "Learn More" + }, + "policy": { + "title": "Filter Notifications from…", + "not_following": { + "title": "People you don't follow", + "subtitle": "Until you manually approve them" + }, + "no_follower": { + "title": "People not following you", + "subtitle": "Including people who have been following you fewer than 3 days" + }, + "new_account": { + "title": "New accounts", + "subtitle": "Created within the past 30 days" + }, + "private_mentions": { + "title": "Unsolicited private mentions", + "subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender" + } } }, "thread": { diff --git a/Localization/app.json b/Localization/app.json index c84499439c..a18a5358b9 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -752,6 +752,25 @@ "silence": "Your account has been limited.", "suspend": "Your account has been suspended.", "learn_more": "Learn More" + }, + "policy": { + "title": "Filter Notifications from…", + "not_following": { + "title": "People you don't follow", + "subtitle": "Until you manually approve them" + }, + "no_follower": { + "title": "People not following you", + "subtitle": "Including people who have been following you fewer than 3 days" + }, + "new_account": { + "title": "New accounts", + "subtitle": "Created within the past 30 days" + }, + "private_mentions": { + "title": "Unsolicited private mentions", + "subtitle": "Filtered unless it’s in reply to your own mention or if you follow the sender" + } } }, "thread": { diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift index 8dbf081d6c..84f2a07bb0 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyHeaderView.swift @@ -1,6 +1,7 @@ // Copyright © 2024 Mastodon gGmbH. All rights reserved. import UIKit +import MastodonLocalization class NotificationPolicyHeaderView: UIView { let titleLabel: UILabel @@ -11,8 +12,7 @@ class NotificationPolicyHeaderView: UIView { titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: .systemFont(ofSize: 20, weight: .bold)) - // TODO: Localization - titleLabel.text = "Filter Notifications from..." + titleLabel.text = L10n.Scene.Notification.Policy.title let buttonImageConfiguration = UIImage diff --git a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift index 0e35041865..22f77332bf 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Policy/NotificationPolicyViewController.swift @@ -17,30 +17,28 @@ enum NotificationFilterItem: Hashable, CaseIterable { case privateMentions var title: String { - // TODO: Localization switch self { case .notFollowing: - return "People you don't follow" + return L10n.Scene.Notification.Policy.NotFollowing.title case .noFollower: - return "People not following you" + return L10n.Scene.Notification.Policy.NoFollower.title case .newAccount: - return "New accounts" + return L10n.Scene.Notification.Policy.NewAccount.title case .privateMentions: - return "Unsolicited private mentions" + return L10n.Scene.Notification.Policy.PrivateMentions.title } } var subtitle: String { - // TODO: Localization switch self { case .notFollowing: - return "Until you manually approve them" + return L10n.Scene.Notification.Policy.NotFollowing.subtitle case .noFollower: - return "Including people who have been following you fewer than 3 days" + return L10n.Scene.Notification.Policy.NoFollower.subtitle case .newAccount: - return "Created within the past 30 days" + return L10n.Scene.Notification.Policy.NewAccount.subtitle case .privateMentions: - return "Filtered unless it’s in reply to your own mention or if you follow the sender" + return L10n.Scene.Notification.Policy.PrivateMentions.subtitle } } } @@ -147,9 +145,6 @@ class NotificationPolicyViewController: UIViewController { @objc private func save(_ sender: UIButton) { guard let authenticationBox = viewModel.appContext.authenticationService.mastodonAuthenticationBoxes.first else { return } - //TODO: Check if this really works. Garbage collection and stuff - self.dismiss(animated:true) - Task { [weak self] in guard let self else { return } @@ -166,11 +161,10 @@ class NotificationPolicyViewController: UIViewController { NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) - } catch { - //TODO: Error Handling - } + } catch {} } + dismiss(animated:true) } @objc private func cancel(_ sender: UIBarButtonItem) { diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 3a8787b50b..b76242fc45 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -925,6 +925,34 @@ public enum L10n { /// request to follow you public static let requestToFollowYou = L10n.tr("Localizable", "Scene.Notification.NotificationDescription.RequestToFollowYou", fallback: "request to follow you") } + public enum Policy { + /// Filter Notifications from… + public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.Title", fallback: "Filter Notifications from…") + 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") + /// New accounts + public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NewAccount.Title", fallback: "New accounts") + } + public enum NoFollower { + /// Including people who have been following you fewer than 3 days + public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NoFollower.Subtitle", fallback: "Including people who have been following you fewer than 3 days") + /// People not following you + public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NoFollower.Title", fallback: "People not following you") + } + public enum NotFollowing { + /// Until you manually approve them + public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.NotFollowing.Subtitle", fallback: "Until you manually approve them") + /// People you don't follow + public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.NotFollowing.Title", fallback: "People you don't follow") + } + public enum PrivateMentions { + /// Filtered unless it’s in reply to your own mention or if you follow the sender + public static let subtitle = L10n.tr("Localizable", "Scene.Notification.Policy.PrivateMentions.Subtitle", fallback: "Filtered unless it’s in reply to your own mention or if you follow the sender") + /// Unsolicited private mentions + public static let title = L10n.tr("Localizable", "Scene.Notification.Policy.PrivateMentions.Title", fallback: "Unsolicited private mentions") + } + } public enum Title { /// Everything public static let everything = L10n.tr("Localizable", "Scene.Notification.Title.Everything", fallback: "Everything") diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 5a6db9ba34..b82367d7f7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -304,11 +304,11 @@ uploaded to Mastodon."; "Scene.Following.Footer" = "Follows from other servers are not displayed."; "Scene.Following.Title" = "following"; "Scene.HomeTimeline.TimelineMenu.Following" = "Following"; -"Scene.HomeTimeline.TimelineMenu.LocalCommunity" = "Local"; -"Scene.HomeTimeline.TimelineMenu.Lists.Title" = "Lists"; -"Scene.HomeTimeline.TimelineMenu.Lists.EmptyMessage" = "You don't have any Lists"; -"Scene.HomeTimeline.TimelineMenu.Hashtags.Title" = "Followed Hashtags"; "Scene.HomeTimeline.TimelineMenu.Hashtags.EmptyMessage" = "You don't follow any Hashtags"; +"Scene.HomeTimeline.TimelineMenu.Hashtags.Title" = "Followed Hashtags"; +"Scene.HomeTimeline.TimelineMenu.Lists.EmptyMessage" = "You don't have any Lists"; +"Scene.HomeTimeline.TimelineMenu.Lists.Title" = "Lists"; +"Scene.HomeTimeline.TimelineMenu.LocalCommunity" = "Local"; "Scene.HomeTimeline.TimelinePill.NewPosts" = "New Posts"; "Scene.HomeTimeline.TimelinePill.Offline" = "Offline"; "Scene.HomeTimeline.TimelinePill.PostSent" = "Post Sent"; @@ -328,6 +328,15 @@ uploaded to Mastodon."; "Scene.Notification.NotificationDescription.PollHasEnded" = "poll has ended"; "Scene.Notification.NotificationDescription.RebloggedYourPost" = "boosted your post"; "Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you"; +"Scene.Notification.Policy.NewAccount.Subtitle" = "Created within the past 30 days"; +"Scene.Notification.Policy.NewAccount.Title" = "New accounts"; +"Scene.Notification.Policy.NoFollower.Subtitle" = "Including people who have been following you fewer than 3 days"; +"Scene.Notification.Policy.NoFollower.Title" = "People not following you"; +"Scene.Notification.Policy.NotFollowing.Subtitle" = "Until you manually approve them"; +"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.Title" = "Filter Notifications from…"; "Scene.Notification.Title.Everything" = "Everything"; "Scene.Notification.Title.Mentions" = "Mentions"; "Scene.Notification.Warning.DeleteStatuses" = "Some of your posts have been removed."; @@ -621,4 +630,4 @@ If you disagree with the policy for **%@**, you can go back and pick a different "Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; -"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; +"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; \ No newline at end of file From ffc80268c5ba69fe411af6f5b5aebb111576518e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 16:32:01 +0200 Subject: [PATCH 36/48] Localization for Banner (IOS-241) --- Localization/StringsConvertor/input/Base.lproj/app.json | 3 +++ Localization/app.json | 3 +++ .../Cell/NotificationFilteringBannerTableViewCell.swift | 5 ++--- .../Sources/MastodonLocalization/Generated/Strings.swift | 4 ++++ .../Resources/Base.lproj/Localizable.strings | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index a18a5358b9..d328f00a69 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -753,6 +753,9 @@ "suspend": "Your account has been suspended.", "learn_more": "Learn More" }, + "filtered_notification_banner": { + "title": "Filtered Notifications" + }, "policy": { "title": "Filter Notifications from…", "not_following": { diff --git a/Localization/app.json b/Localization/app.json index a18a5358b9..d328f00a69 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -753,6 +753,9 @@ "suspend": "Your account has been suspended.", "learn_more": "Learn More" }, + "filtered_notification_banner": { + "title": "Filtered Notifications" + }, "policy": { "title": "Filter Notifications from…", "not_following": { diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index 8892c12d58..e18ccf5a18 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -3,6 +3,7 @@ import UIKit import MastodonSDK import MastodonUI +import MastodonLocalization class NotificationFilteringBannerTableViewCell: UITableViewCell { @@ -29,8 +30,7 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { iconImageWrapperView.addSubview(iconImageView) titleLabel = UILabel() - //TODO: Add localization - titleLabel.text = "Filtered Notifications" + titleLabel.text = L10n.Scene.Notification.FilteredNotificationBanner.title titleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) subtitleLabel = UILabel() @@ -85,7 +85,6 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { } func configure(with policy: Mastodon.Entity.NotificationPolicy) { - //TODO: Add localization subtitleLabel.text = "\(policy.summary.pendingRequestsCount) people you may know" } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index b76242fc45..cc51fc6b73 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -895,6 +895,10 @@ public enum L10n { } } public enum Notification { + public enum FilteredNotificationBanner { + /// Filtered Notifications + public static let title = L10n.tr("Localizable", "Scene.Notification.FilteredNotificationBanner.Title", fallback: "Filtered Notifications") + } public enum FollowRequest { /// Accept public static let accept = L10n.tr("Localizable", "Scene.Notification.FollowRequest.Accept", fallback: "Accept") diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index b82367d7f7..0c7b4bc42a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -316,6 +316,7 @@ uploaded to Mastodon."; "Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; "Scene.Login.Subtitle" = "Log in with the server where you created your account."; "Scene.Login.Title" = "Welcome Back"; +"Scene.Notification.FilteredNotificationBanner.Title" = "Filtered Notifications"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; From 287587fcf1b2d4ccac40e0b8b947f7f338313b3e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 17:37:32 +0200 Subject: [PATCH 37/48] [WIP] Plural Localization for banner-subtitle (IOS-241) Caveat: doesn't work yet :clown_face: --- Localization/Localizable.stringsdict | 1178 +++++++++-------- .../input/Base.lproj/Localizable.stringsdict | 1178 +++++++++-------- ...ficationFilteringBannerTableViewCell.swift | 2 +- .../Generated/Strings.swift | 6 + .../Base.lproj/Localizable.stringsdict | 1178 +++++++++-------- 5 files changed, 1798 insertions(+), 1744 deletions(-) diff --git a/Localization/Localizable.stringsdict b/Localization/Localizable.stringsdict index a56e4b1e64..1038e2ea85 100644 --- a/Localization/Localizable.stringsdict +++ b/Localization/Localizable.stringsdict @@ -2,72 +2,72 @@ - a11y.plural.count.unread.notification - - NSStringLocalizedFormatKey - %#@notification_count_unread_notification@ - notification_count_unread_notification - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - no unread notifications - one - 1 unread notification - few - %ld unread notifications - many - %ld unread notifications - other - %ld unread notifications - - - a11y.plural.count.input_limit_exceeds - - NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - - a11y.plural.count.input_limit_remains - - NSStringLocalizedFormatKey - Input limit remains %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + no unread notifications + one + 1 unread notification + few + %ld unread notifications + many + %ld unread notifications + other + %ld unread notifications + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Input limit exceeds %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Input limit remains %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + a11y.plural.count.characters_left NSStringLocalizedFormatKey @@ -90,125 +90,125 @@ %ld characters left - plural.count.followed_by_and_mutual - - NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ - names - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - other - - - count_mutual - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - Followed by %1$@ - one - Followed by %1$@, and another mutual - few - Followed by %1$@, and %ld mutuals - many - Followed by %1$@, and %ld mutuals - other - Followed by %1$@, and %ld mutuals - - - plural.count.metric_formatted.post - - NSStringLocalizedFormatKey - %@ %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - posts - one - post - few - posts - many - posts - other - posts - - - plural.count.media - - NSStringLocalizedFormatKey - %#@media_count@ - media_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 media - one - 1 media - few - %ld media - many - %ld media - other - %ld media - - - plural.count.post - - NSStringLocalizedFormatKey - %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 posts - one - 1 post - few - %ld posts - many - %ld posts - other - %ld posts - - + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + Followed by %1$@ + one + Followed by %1$@, and another mutual + few + Followed by %1$@, and %ld mutuals + many + Followed by %1$@, and %ld mutuals + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + posts + one + post + few + posts + many + posts + other + posts + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 media + one + 1 media + few + %ld media + many + %ld media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 posts + one + 1 post + few + %ld posts + many + %ld posts + other + %ld posts + + plural.count.favorite - - NSStringLocalizedFormatKey - %#@favorite_count@ - favorite_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 favorites - one - 1 favorite - few - %ld favorites - many - %ld favorites - other - %ld favorites - - + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 favorites + one + 1 favorite + few + %ld favorites + many + %ld favorites + other + %ld favorites + + plural.count.reblog NSStringLocalizedFormatKey @@ -231,29 +231,29 @@ %ld reblogs - plural.count.reblog_a11y - - NSStringLocalizedFormatKey - %#@reblog_count@ - reblog_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 re-blogs - one - 1 re-blog - few - %ld re-blogs - many - %ld re-blogs - other - %ld re-blogs - - - plural.count.reply + plural.count.reblog_a11y + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 re-blogs + one + 1 re-blog + few + %ld re-blogs + many + %ld re-blogs + other + %ld re-blogs + + + plural.count.reply NSStringLocalizedFormatKey %#@reply_count@ @@ -275,379 +275,395 @@ %ld replies - plural.count.vote - - NSStringLocalizedFormatKey - %#@vote_count@ - vote_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 votes - one - 1 vote - few - %ld votes - many - %ld votes - other - %ld votes - - - plural.count.voter - - NSStringLocalizedFormatKey - %#@voter_count@ - voter_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 voters - one - 1 voter - few - %ld voters - many - %ld voters - other - %ld voters - - - plural.people_talking - - NSStringLocalizedFormatKey - %#@count_people_talking@ - count_people_talking - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 people talking - one - 1 people talking - few - %ld people talking - many - %ld people talking - other - %ld people talking - - - plural.count.following - - NSStringLocalizedFormatKey - %#@count_following@ - count_following - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 following - one - 1 following - few - %ld following - many - %ld following - other - %ld following - - - plural.count.follower - - NSStringLocalizedFormatKey - %#@count_follower@ - count_follower - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 followers - one - 1 follower - few - %ld followers - many - %ld followers - other - %ld followers - - - date.year.left - - NSStringLocalizedFormatKey - %#@count_year_left@ - count_year_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 years left - one - 1 year left - few - %ld years left - many - %ld years left - other - %ld years left - - - date.month.left - - NSStringLocalizedFormatKey - %#@count_month_left@ - count_month_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 months left - one - 1 months left - few - %ld months left - many - %ld months left - other - %ld months left - - - date.day.left - - NSStringLocalizedFormatKey - %#@count_day_left@ - count_day_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 days left - one - 1 day left - few - %ld days left - many - %ld days left - other - %ld days left - - - date.hour.left - - NSStringLocalizedFormatKey - %#@count_hour_left@ - count_hour_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 hours left - one - 1 hour left - few - %ld hours left - many - %ld hours left - other - %ld hours left - - - date.minute.left - - NSStringLocalizedFormatKey - %#@count_minute_left@ - count_minute_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 minutes left - one - 1 minute left - few - %ld minutes left - many - %ld minutes left - other - %ld minutes left - - - date.second.left - - NSStringLocalizedFormatKey - %#@count_second_left@ - count_second_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 seconds left - one - 1 second left - few - %ld seconds left - many - %ld seconds left - other - %ld seconds left - - - date.year.ago.abbr - - NSStringLocalizedFormatKey - %#@count_year_ago_abbr@ - count_year_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0y ago - one - 1y ago - few - %ldy ago - many - %ldy ago - other - %ldy ago - - - date.month.ago.abbr - - NSStringLocalizedFormatKey - %#@count_month_ago_abbr@ - count_month_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0M ago - one - 1M ago - few - %ldM ago - many - %ldM ago - other - %ldM ago - - - date.day.ago.abbr - - NSStringLocalizedFormatKey - %#@count_day_ago_abbr@ - count_day_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0d ago - one - 1d ago - few - %ldd ago - many - %ldd ago - other - %ldd ago - - - date.hour.ago.abbr - - NSStringLocalizedFormatKey - %#@count_hour_ago_abbr@ - count_hour_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0h ago - one - 1h ago - few - %ldh ago - many - %ldh ago - other - %ldh ago - - - date.minute.ago.abbr - - NSStringLocalizedFormatKey - %#@count_minute_ago_abbr@ - count_minute_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0m ago - one - 1m ago - few - %ldm ago - many - %ldm ago - other - %ldm ago - - - date.second.ago.abbr - - NSStringLocalizedFormatKey - %#@count_second_ago_abbr@ - count_second_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0s ago - one - 1s ago - few - %lds ago - many - %lds ago - other - %lds ago - - + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 votes + one + 1 vote + few + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 voters + one + 1 voter + few + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 people talking + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 following + one + 1 following + few + %ld following + many + %ld following + other + %ld following + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 followers + one + 1 follower + few + %ld followers + many + %ld followers + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 years left + one + 1 year left + few + %ld years left + many + %ld years left + other + %ld years left + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 months left + one + 1 months left + few + %ld months left + many + %ld months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 days left + one + 1 day left + few + %ld days left + many + %ld days left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 hours left + one + 1 hour left + few + %ld hours left + many + %ld hours left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 minutes left + one + 1 minute left + few + %ld minutes left + many + %ld minutes left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 seconds left + one + 1 second left + few + %ld seconds left + many + %ld seconds left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0y ago + one + 1y ago + few + %ldy ago + many + %ldy ago + other + %ldy ago + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0M ago + one + 1M ago + few + %ldM ago + many + %ldM ago + other + %ldM ago + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0d ago + one + 1d ago + few + %ldd ago + many + %ldd ago + other + %ldd ago + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0h ago + one + 1h ago + few + %ldh ago + many + %ldh ago + other + %ldh ago + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0m ago + one + 1m ago + few + %ldm ago + many + %ldm ago + other + %ldm ago + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0s ago + one + 1s ago + few + %lds ago + many + %lds ago + other + %lds ago + + + plural.filtered_notification_banner.subtitle + + NSStringLocalizedFormatKey + %#@number_of_requests@ + number_of_requests + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + One person you may know + other + %ld people you may know + + diff --git a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict index a56e4b1e64..1038e2ea85 100644 --- a/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/Base.lproj/Localizable.stringsdict @@ -2,72 +2,72 @@ - a11y.plural.count.unread.notification - - NSStringLocalizedFormatKey - %#@notification_count_unread_notification@ - notification_count_unread_notification - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - no unread notifications - one - 1 unread notification - few - %ld unread notifications - many - %ld unread notifications - other - %ld unread notifications - - - a11y.plural.count.input_limit_exceeds - - NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - - a11y.plural.count.input_limit_remains - - NSStringLocalizedFormatKey - Input limit remains %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + no unread notifications + one + 1 unread notification + few + %ld unread notifications + many + %ld unread notifications + other + %ld unread notifications + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Input limit exceeds %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Input limit remains %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + a11y.plural.count.characters_left NSStringLocalizedFormatKey @@ -90,125 +90,125 @@ %ld characters left - plural.count.followed_by_and_mutual - - NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ - names - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - other - - - count_mutual - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - Followed by %1$@ - one - Followed by %1$@, and another mutual - few - Followed by %1$@, and %ld mutuals - many - Followed by %1$@, and %ld mutuals - other - Followed by %1$@, and %ld mutuals - - - plural.count.metric_formatted.post - - NSStringLocalizedFormatKey - %@ %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - posts - one - post - few - posts - many - posts - other - posts - - - plural.count.media - - NSStringLocalizedFormatKey - %#@media_count@ - media_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 media - one - 1 media - few - %ld media - many - %ld media - other - %ld media - - - plural.count.post - - NSStringLocalizedFormatKey - %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 posts - one - 1 post - few - %ld posts - many - %ld posts - other - %ld posts - - + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + Followed by %1$@ + one + Followed by %1$@, and another mutual + few + Followed by %1$@, and %ld mutuals + many + Followed by %1$@, and %ld mutuals + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + posts + one + post + few + posts + many + posts + other + posts + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 media + one + 1 media + few + %ld media + many + %ld media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 posts + one + 1 post + few + %ld posts + many + %ld posts + other + %ld posts + + plural.count.favorite - - NSStringLocalizedFormatKey - %#@favorite_count@ - favorite_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 favorites - one - 1 favorite - few - %ld favorites - many - %ld favorites - other - %ld favorites - - + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 favorites + one + 1 favorite + few + %ld favorites + many + %ld favorites + other + %ld favorites + + plural.count.reblog NSStringLocalizedFormatKey @@ -231,29 +231,29 @@ %ld reblogs - plural.count.reblog_a11y - - NSStringLocalizedFormatKey - %#@reblog_count@ - reblog_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 re-blogs - one - 1 re-blog - few - %ld re-blogs - many - %ld re-blogs - other - %ld re-blogs - - - plural.count.reply + plural.count.reblog_a11y + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 re-blogs + one + 1 re-blog + few + %ld re-blogs + many + %ld re-blogs + other + %ld re-blogs + + + plural.count.reply NSStringLocalizedFormatKey %#@reply_count@ @@ -275,379 +275,395 @@ %ld replies - plural.count.vote - - NSStringLocalizedFormatKey - %#@vote_count@ - vote_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 votes - one - 1 vote - few - %ld votes - many - %ld votes - other - %ld votes - - - plural.count.voter - - NSStringLocalizedFormatKey - %#@voter_count@ - voter_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 voters - one - 1 voter - few - %ld voters - many - %ld voters - other - %ld voters - - - plural.people_talking - - NSStringLocalizedFormatKey - %#@count_people_talking@ - count_people_talking - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 people talking - one - 1 people talking - few - %ld people talking - many - %ld people talking - other - %ld people talking - - - plural.count.following - - NSStringLocalizedFormatKey - %#@count_following@ - count_following - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 following - one - 1 following - few - %ld following - many - %ld following - other - %ld following - - - plural.count.follower - - NSStringLocalizedFormatKey - %#@count_follower@ - count_follower - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 followers - one - 1 follower - few - %ld followers - many - %ld followers - other - %ld followers - - - date.year.left - - NSStringLocalizedFormatKey - %#@count_year_left@ - count_year_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 years left - one - 1 year left - few - %ld years left - many - %ld years left - other - %ld years left - - - date.month.left - - NSStringLocalizedFormatKey - %#@count_month_left@ - count_month_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 months left - one - 1 months left - few - %ld months left - many - %ld months left - other - %ld months left - - - date.day.left - - NSStringLocalizedFormatKey - %#@count_day_left@ - count_day_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 days left - one - 1 day left - few - %ld days left - many - %ld days left - other - %ld days left - - - date.hour.left - - NSStringLocalizedFormatKey - %#@count_hour_left@ - count_hour_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 hours left - one - 1 hour left - few - %ld hours left - many - %ld hours left - other - %ld hours left - - - date.minute.left - - NSStringLocalizedFormatKey - %#@count_minute_left@ - count_minute_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 minutes left - one - 1 minute left - few - %ld minutes left - many - %ld minutes left - other - %ld minutes left - - - date.second.left - - NSStringLocalizedFormatKey - %#@count_second_left@ - count_second_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 seconds left - one - 1 second left - few - %ld seconds left - many - %ld seconds left - other - %ld seconds left - - - date.year.ago.abbr - - NSStringLocalizedFormatKey - %#@count_year_ago_abbr@ - count_year_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0y ago - one - 1y ago - few - %ldy ago - many - %ldy ago - other - %ldy ago - - - date.month.ago.abbr - - NSStringLocalizedFormatKey - %#@count_month_ago_abbr@ - count_month_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0M ago - one - 1M ago - few - %ldM ago - many - %ldM ago - other - %ldM ago - - - date.day.ago.abbr - - NSStringLocalizedFormatKey - %#@count_day_ago_abbr@ - count_day_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0d ago - one - 1d ago - few - %ldd ago - many - %ldd ago - other - %ldd ago - - - date.hour.ago.abbr - - NSStringLocalizedFormatKey - %#@count_hour_ago_abbr@ - count_hour_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0h ago - one - 1h ago - few - %ldh ago - many - %ldh ago - other - %ldh ago - - - date.minute.ago.abbr - - NSStringLocalizedFormatKey - %#@count_minute_ago_abbr@ - count_minute_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0m ago - one - 1m ago - few - %ldm ago - many - %ldm ago - other - %ldm ago - - - date.second.ago.abbr - - NSStringLocalizedFormatKey - %#@count_second_ago_abbr@ - count_second_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0s ago - one - 1s ago - few - %lds ago - many - %lds ago - other - %lds ago - - + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 votes + one + 1 vote + few + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 voters + one + 1 voter + few + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 people talking + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 following + one + 1 following + few + %ld following + many + %ld following + other + %ld following + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 followers + one + 1 follower + few + %ld followers + many + %ld followers + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 years left + one + 1 year left + few + %ld years left + many + %ld years left + other + %ld years left + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 months left + one + 1 months left + few + %ld months left + many + %ld months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 days left + one + 1 day left + few + %ld days left + many + %ld days left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 hours left + one + 1 hour left + few + %ld hours left + many + %ld hours left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 minutes left + one + 1 minute left + few + %ld minutes left + many + %ld minutes left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 seconds left + one + 1 second left + few + %ld seconds left + many + %ld seconds left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0y ago + one + 1y ago + few + %ldy ago + many + %ldy ago + other + %ldy ago + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0M ago + one + 1M ago + few + %ldM ago + many + %ldM ago + other + %ldM ago + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0d ago + one + 1d ago + few + %ldd ago + many + %ldd ago + other + %ldd ago + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0h ago + one + 1h ago + few + %ldh ago + many + %ldh ago + other + %ldh ago + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0m ago + one + 1m ago + few + %ldm ago + many + %ldm ago + other + %ldm ago + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0s ago + one + 1s ago + few + %lds ago + many + %lds ago + other + %lds ago + + + plural.filtered_notification_banner.subtitle + + NSStringLocalizedFormatKey + %#@number_of_requests@ + number_of_requests + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + One person you may know + other + %ld people you may know + + diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index e18ccf5a18..e24ddd087b 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -85,6 +85,6 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { } func configure(with policy: Mastodon.Entity.NotificationPolicy) { - subtitleLabel.text = "\(policy.summary.pendingRequestsCount) people you may know" + subtitleLabel.text = L10n.Plural.FilteredNotificationBanner.subtitle(policy.summary.pendingRequestsCount) } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index cc51fc6b73..509431c5f7 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1982,6 +1982,12 @@ public enum L10n { } } } + public enum FilteredNotificationBanner { + /// Plural format key: "%#@number_of_requests@" + public static func subtitle(_ p1: Int) -> String { + return L10n.tr("Localizable", "plural.filtered_notification_banner.subtitle", p1, fallback: "Plural format key: \"%#@number_of_requests@\"") + } + } } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict index a56e4b1e64..1038e2ea85 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.stringsdict @@ -2,72 +2,72 @@ - a11y.plural.count.unread.notification - - NSStringLocalizedFormatKey - %#@notification_count_unread_notification@ - notification_count_unread_notification - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - no unread notifications - one - 1 unread notification - few - %ld unread notifications - many - %ld unread notifications - other - %ld unread notifications - - - a11y.plural.count.input_limit_exceeds - - NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - - a11y.plural.count.input_limit_remains - - NSStringLocalizedFormatKey - Input limit remains %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 characters - one - 1 character - few - %ld characters - many - %ld characters - other - %ld characters - - + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + no unread notifications + one + 1 unread notification + few + %ld unread notifications + many + %ld unread notifications + other + %ld unread notifications + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Input limit exceeds %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Input limit remains %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 characters + one + 1 character + few + %ld characters + many + %ld characters + other + %ld characters + + a11y.plural.count.characters_left NSStringLocalizedFormatKey @@ -90,125 +90,125 @@ %ld characters left - plural.count.followed_by_and_mutual - - NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ - names - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - other - - - count_mutual - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - Followed by %1$@ - one - Followed by %1$@, and another mutual - few - Followed by %1$@, and %ld mutuals - many - Followed by %1$@, and %ld mutuals - other - Followed by %1$@, and %ld mutuals - - - plural.count.metric_formatted.post - - NSStringLocalizedFormatKey - %@ %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - posts - one - post - few - posts - many - posts - other - posts - - - plural.count.media - - NSStringLocalizedFormatKey - %#@media_count@ - media_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 media - one - 1 media - few - %ld media - many - %ld media - other - %ld media - - - plural.count.post - - NSStringLocalizedFormatKey - %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 posts - one - 1 post - few - %ld posts - many - %ld posts - other - %ld posts - - + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + Followed by %1$@ + one + Followed by %1$@, and another mutual + few + Followed by %1$@, and %ld mutuals + many + Followed by %1$@, and %ld mutuals + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + posts + one + post + few + posts + many + posts + other + posts + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 media + one + 1 media + few + %ld media + many + %ld media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 posts + one + 1 post + few + %ld posts + many + %ld posts + other + %ld posts + + plural.count.favorite - - NSStringLocalizedFormatKey - %#@favorite_count@ - favorite_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 favorites - one - 1 favorite - few - %ld favorites - many - %ld favorites - other - %ld favorites - - + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 favorites + one + 1 favorite + few + %ld favorites + many + %ld favorites + other + %ld favorites + + plural.count.reblog NSStringLocalizedFormatKey @@ -231,29 +231,29 @@ %ld reblogs - plural.count.reblog_a11y - - NSStringLocalizedFormatKey - %#@reblog_count@ - reblog_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 re-blogs - one - 1 re-blog - few - %ld re-blogs - many - %ld re-blogs - other - %ld re-blogs - - - plural.count.reply + plural.count.reblog_a11y + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 re-blogs + one + 1 re-blog + few + %ld re-blogs + many + %ld re-blogs + other + %ld re-blogs + + + plural.count.reply NSStringLocalizedFormatKey %#@reply_count@ @@ -275,379 +275,395 @@ %ld replies - plural.count.vote - - NSStringLocalizedFormatKey - %#@vote_count@ - vote_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 votes - one - 1 vote - few - %ld votes - many - %ld votes - other - %ld votes - - - plural.count.voter - - NSStringLocalizedFormatKey - %#@voter_count@ - voter_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 voters - one - 1 voter - few - %ld voters - many - %ld voters - other - %ld voters - - - plural.people_talking - - NSStringLocalizedFormatKey - %#@count_people_talking@ - count_people_talking - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 people talking - one - 1 people talking - few - %ld people talking - many - %ld people talking - other - %ld people talking - - - plural.count.following - - NSStringLocalizedFormatKey - %#@count_following@ - count_following - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 following - one - 1 following - few - %ld following - many - %ld following - other - %ld following - - - plural.count.follower - - NSStringLocalizedFormatKey - %#@count_follower@ - count_follower - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 followers - one - 1 follower - few - %ld followers - many - %ld followers - other - %ld followers - - - date.year.left - - NSStringLocalizedFormatKey - %#@count_year_left@ - count_year_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 years left - one - 1 year left - few - %ld years left - many - %ld years left - other - %ld years left - - - date.month.left - - NSStringLocalizedFormatKey - %#@count_month_left@ - count_month_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 months left - one - 1 months left - few - %ld months left - many - %ld months left - other - %ld months left - - - date.day.left - - NSStringLocalizedFormatKey - %#@count_day_left@ - count_day_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 days left - one - 1 day left - few - %ld days left - many - %ld days left - other - %ld days left - - - date.hour.left - - NSStringLocalizedFormatKey - %#@count_hour_left@ - count_hour_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 hours left - one - 1 hour left - few - %ld hours left - many - %ld hours left - other - %ld hours left - - - date.minute.left - - NSStringLocalizedFormatKey - %#@count_minute_left@ - count_minute_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 minutes left - one - 1 minute left - few - %ld minutes left - many - %ld minutes left - other - %ld minutes left - - - date.second.left - - NSStringLocalizedFormatKey - %#@count_second_left@ - count_second_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0 seconds left - one - 1 second left - few - %ld seconds left - many - %ld seconds left - other - %ld seconds left - - - date.year.ago.abbr - - NSStringLocalizedFormatKey - %#@count_year_ago_abbr@ - count_year_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0y ago - one - 1y ago - few - %ldy ago - many - %ldy ago - other - %ldy ago - - - date.month.ago.abbr - - NSStringLocalizedFormatKey - %#@count_month_ago_abbr@ - count_month_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0M ago - one - 1M ago - few - %ldM ago - many - %ldM ago - other - %ldM ago - - - date.day.ago.abbr - - NSStringLocalizedFormatKey - %#@count_day_ago_abbr@ - count_day_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0d ago - one - 1d ago - few - %ldd ago - many - %ldd ago - other - %ldd ago - - - date.hour.ago.abbr - - NSStringLocalizedFormatKey - %#@count_hour_ago_abbr@ - count_hour_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0h ago - one - 1h ago - few - %ldh ago - many - %ldh ago - other - %ldh ago - - - date.minute.ago.abbr - - NSStringLocalizedFormatKey - %#@count_minute_ago_abbr@ - count_minute_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0m ago - one - 1m ago - few - %ldm ago - many - %ldm ago - other - %ldm ago - - - date.second.ago.abbr - - NSStringLocalizedFormatKey - %#@count_second_ago_abbr@ - count_second_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - zero - 0s ago - one - 1s ago - few - %lds ago - many - %lds ago - other - %lds ago - - + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 votes + one + 1 vote + few + %ld votes + many + %ld votes + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 voters + one + 1 voter + few + %ld voters + many + %ld voters + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 people talking + one + 1 people talking + few + %ld people talking + many + %ld people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 following + one + 1 following + few + %ld following + many + %ld following + other + %ld following + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 followers + one + 1 follower + few + %ld followers + many + %ld followers + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 years left + one + 1 year left + few + %ld years left + many + %ld years left + other + %ld years left + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 months left + one + 1 months left + few + %ld months left + many + %ld months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 days left + one + 1 day left + few + %ld days left + many + %ld days left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 hours left + one + 1 hour left + few + %ld hours left + many + %ld hours left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 minutes left + one + 1 minute left + few + %ld minutes left + many + %ld minutes left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0 seconds left + one + 1 second left + few + %ld seconds left + many + %ld seconds left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0y ago + one + 1y ago + few + %ldy ago + many + %ldy ago + other + %ldy ago + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0M ago + one + 1M ago + few + %ldM ago + many + %ldM ago + other + %ldM ago + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0d ago + one + 1d ago + few + %ldd ago + many + %ldd ago + other + %ldd ago + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0h ago + one + 1h ago + few + %ldh ago + many + %ldh ago + other + %ldh ago + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0m ago + one + 1m ago + few + %ldm ago + many + %ldm ago + other + %ldm ago + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + zero + 0s ago + one + 1s ago + few + %lds ago + many + %lds ago + other + %lds ago + + + plural.filtered_notification_banner.subtitle + + NSStringLocalizedFormatKey + %#@number_of_requests@ + number_of_requests + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + One person you may know + other + %ld people you may know + + From f5bc847b301cc71799947728775c6340757fa633 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 Jul 2024 17:39:00 +0200 Subject: [PATCH 38/48] Reverse buttons (IOS-241) --- .../Requests/NotificationRequestTableViewCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 4d2a189590..0bb49f7f2d 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -103,7 +103,7 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestActivityIndicatorView.stopAnimating() rejectNotificationRequestButton.addSubview(rejectNotificationRequestActivityIndicatorView) - buttonStackView = UIStackView(arrangedSubviews: [acceptNotificationRequestButtonShadowBackgroundContainer, rejectNotificationRequestButtonShadowBackgroundContainer]) + buttonStackView = UIStackView(arrangedSubviews: [rejectNotificationRequestButtonShadowBackgroundContainer, acceptNotificationRequestButtonShadowBackgroundContainer]) buttonStackView.axis = .horizontal buttonStackView.distribution = .fillEqually buttonStackView.spacing = 16 From ff48cc2f01bd50f05cd41ed765b3e7ff6d3ecf73 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 09:44:08 +0200 Subject: [PATCH 39/48] Add swipe-action to dismiss (IOS-241) --- ...otificationRequestsTableViewController.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index c19c0a8a66..fd216279fd 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -3,6 +3,7 @@ import UIKit import MastodonSDK import MastodonCore +import MastodonAsset enum NotificationRequestsSection: Hashable { case main @@ -84,6 +85,22 @@ extension NotificationRequestsTableViewController: UITableViewDelegate { await DataSourceFacade.coordinateToNotificationRequest(request: request, provider: self) } } + + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let dismissAction = UIContextualAction(style: .normal, title: "Dismiss") { [weak self] action, view, completion in + guard let request = self?.viewModel.requests[indexPath.row], let cell = tableView.cellForRow(at: indexPath) as? NotificationRequestTableViewCell else { return completion(false) } + + self?.rejectNotificationRequest(cell, notificationRequest: request) + completion(true) + } + + dismissAction.image = UIImage(systemName: "speaker.slash") + + let swipeAction = UISwipeActionsConfiguration(actions: [dismissAction]) + swipeAction.performsFirstActionWithFullSwipe = true + return swipeAction + + } } // MARK: - AuthContextProvider From f3c035ce92ffe3aebaee6c107cc3e5760f5ce985 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 12:09:24 +0200 Subject: [PATCH 40/48] Add accept/dismiss-menu (IOS-241) --- Mastodon.xcodeproj/project.pbxproj | 12 +++ Mastodon/Coordinator/SceneCoordinator.swift | 6 +- .../DataSourceFacade+Notifications.swift | 8 +- ...ntNotificationTimelineViewController.swift | 54 +++++++++++++ .../NotificationRequestTableViewCell.swift | 2 + ...ificationRequestsTableViewController.swift | 78 ++++++++++++------- .../NotificationTimelineViewController.swift | 2 +- 7 files changed, 128 insertions(+), 34 deletions(-) create mode 100644 Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 426914cb3f..8db2635d6d 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -170,6 +170,7 @@ D85DF9742C481B3500A01408 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */; }; D85DF9762C4965A900A01408 /* NotificationRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */; }; D85DF97A2C4E49A400A01408 /* NotificationRequestCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */; }; + D85DF97E2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */; }; D87364F92AE28DB500C8F919 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = D87364F82AE28DB500C8F919 /* Kanna */; }; D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; }; D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; }; @@ -817,6 +818,7 @@ D85DF9732C481B3500A01408 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsViewModel.swift; sourceTree = ""; }; D85DF9792C4E49A400A01408 /* NotificationRequestCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestCountView.swift; sourceTree = ""; }; + D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNotificationTimelineViewController.swift; sourceTree = ""; }; D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = ""; }; D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = ""; }; D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = ""; }; @@ -1852,6 +1854,7 @@ D85DF9702C481B1100A01408 /* Requests */ = { isa = PBXGroup; children = ( + D85DF97C2C50EF8700A01408 /* Account Notifications */, D85DF96E2C481B1100A01408 /* NotificationRequestsTableViewController.swift */, D85DF96F2C481B1100A01408 /* NotificationRequestTableViewCell.swift */, D85DF9752C4965A900A01408 /* NotificationRequestsViewModel.swift */, @@ -1861,6 +1864,14 @@ path = "Notification Filtering/Requests"; sourceTree = ""; }; + D85DF97C2C50EF8700A01408 /* Account Notifications */ = { + isa = PBXGroup; + children = ( + D85DF97D2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift */, + ); + path = "Account Notifications"; + sourceTree = ""; + }; D8A6AB68291C50F3003AB663 /* Login */ = { isa = PBXGroup; children = ( @@ -3816,6 +3827,7 @@ DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */, 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */, DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */, + D85DF97E2C50EFA700A01408 /* AccountNotificationTimelineViewController.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 3f4f8626f4..4ca70d8ec9 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -209,7 +209,7 @@ extension SceneCoordinator { // Notifications case notificationPolicy(viewModel: NotificationFilterViewModel) case notificationRequests(viewModel: NotificationRequestsViewModel) - case notificationTimeline(viewModel: NotificationTimelineViewModel) + case accountNotificationTimeline(viewModel: NotificationTimelineViewModel, request: Mastodon.Entity.NotificationRequest) // report case report(viewModel: ReportViewModel) @@ -567,8 +567,8 @@ private extension SceneCoordinator { viewController = NotificationRequestsTableViewController(viewModel: viewModel) case .notificationPolicy(let viewModel): viewController = NotificationPolicyViewController(viewModel: viewModel) - case .notificationTimeline(let viewModel): - viewController = NotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self) + case .accountNotificationTimeline(let viewModel, let request): + viewController = AccountNotificationTimelineViewController(viewModel: viewModel, context: appContext, coordinator: self, notificationRequest: request) } setupDependency(for: viewController as? NeedsDependency) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index 15ef9f1e52..f6dac507cc 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -28,13 +28,17 @@ extension DataSourceFacade { static func coordinateToNotificationRequest( request: Mastodon.Entity.NotificationRequest, provider: ViewControllerWithDependencies & AuthContextProvider - ) async { + ) async -> AccountNotificationTimelineViewController? { provider.coordinator.showLoading() let notificationTimelineViewModel = NotificationTimelineViewModel(context: provider.context, authContext: provider.authContext, scope: .fromAccount(request.account)) provider.coordinator.hideLoading() - provider.coordinator.present(scene: .notificationTimeline(viewModel: notificationTimelineViewModel), transition: .show) + + guard let viewController = provider.coordinator.present(scene: .accountNotificationTimeline(viewModel: notificationTimelineViewModel, request: request), transition: .show) as? AccountNotificationTimelineViewController else { return nil } + + return viewController + } } diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift new file mode 100644 index 0000000000..0118c25e29 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift @@ -0,0 +1,54 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonCore +import MastodonSDK + +protocol AccountNotificationTimelineViewControllerDelegate: AnyObject { + func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) + func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) +} + +class AccountNotificationTimelineViewController: NotificationTimelineViewController { + + let request: Mastodon.Entity.NotificationRequest + weak var delegate: AccountNotificationTimelineViewControllerDelegate? + + init(viewModel: NotificationTimelineViewModel, context: AppContext, coordinator: SceneCoordinator, notificationRequest: Mastodon.Entity.NotificationRequest) { + self.request = notificationRequest + + super.init(viewModel: viewModel, context: context, coordinator: coordinator) + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, image: UIImage(systemName: "ellipsis.circle"), target: nil, action: nil, menu: menu()) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + // MARK: - Actions + + //TODO: Localization + func menu() -> UIMenu { + let menu = UIMenu(children: [ + UIAction(title: "Accept", image: UIImage(systemName: "checkmark")) { [weak self] _ in + guard let self else { return } + + coordinator.showLoading() + self.delegate?.acceptRequest(self, request: request) { + self.navigationController?.popViewController(animated: true) + } + coordinator.hideLoading() + }, + UIAction(title: "Dismiss", image: UIImage(systemName: "speaker.slash")) { [weak self] _ in + guard let self else { return } + + coordinator.showLoading() + self.delegate?.dismissRequest(self, request: request) { + self.navigationController?.popViewController(animated: true) + } + coordinator.hideLoading() + } + ]) + + return menu + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 0bb49f7f2d..95b23e5066 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -57,6 +57,7 @@ class NotificationRequestTableViewCell: UITableViewCell { acceptNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false acceptNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) acceptNotificationRequestButton.setTitleColor(.white, for: .normal) + //TODO: Localization acceptNotificationRequestButton.setTitle("Accept", for: .normal) acceptNotificationRequestButton.setImage(UIImage(systemName: "checkmark"), for: .normal) acceptNotificationRequestButton.imageView?.contentMode = .scaleAspectFit @@ -82,6 +83,7 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) rejectNotificationRequestButton.setTitleColor(.black, for: .normal) + //TODO: Localization rejectNotificationRequestButton.setTitle("Dismiss", for: .normal) rejectNotificationRequestButton.setImage(UIImage(systemName: "speaker.slash"), for: .normal) rejectNotificationRequestButton.imageView?.contentMode = .scaleAspectFit diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index fd216279fd..597535aeb0 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -4,6 +4,7 @@ import UIKit import MastodonSDK import MastodonCore import MastodonAsset +import MastodonLocalization enum NotificationRequestsSection: Hashable { case main @@ -58,8 +59,7 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency tableView.delegate = self self.dataSource = dataSource - //TODO: Localization - title = "Filtered Notifications" + title = L10n.Scene.Notification.FilteredNotificationBanner.title } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -81,8 +81,11 @@ extension NotificationRequestsTableViewController: UITableViewDelegate { let request = viewModel.requests[indexPath.row] - Task { - await DataSourceFacade.coordinateToNotificationRequest(request: request, provider: self) + Task { [weak self] in + guard let self else { return } + + let viewController = await DataSourceFacade.coordinateToNotificationRequest(request: request, provider: self) + viewController?.delegate = self } } @@ -167,30 +170,7 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC Task { [weak self] in guard let self else { return } do { - _ = try await context.apiService.rejectNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, - id: notificationRequest.id) - - let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value - - NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) - - if requests.count > 0 { - - await MainActor.run { [weak self] in - guard let self else { return } - self.viewModel.requests = requests - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) - - self.dataSource?.apply(snapshot) - } - } else { - await MainActor.run { [weak self] in - _ = self?.navigationController?.popViewController(animated: true) - } - } - + try await rejectNotificationRequest(notificationRequest) } catch { cell.rejectNotificationRequestActivityIndicatorView.stopAnimating() cell.rejectNotificationRequestButton.tintColor = .black @@ -200,4 +180,46 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC } } } + + private func rejectNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws { + _ = try await context.apiService.rejectNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, + id: notificationRequest.id) + + let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) + + if requests.count > 0 { + await MainActor.run { [weak self] in + guard let self else { return } + self.viewModel.requests = requests + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + self.dataSource?.apply(snapshot) + } + } else { + await MainActor.run { [weak self] in + _ = self?.navigationController?.popViewController(animated: true) + } + } + + } +} + +extension NotificationRequestsTableViewController: AccountNotificationTimelineViewControllerDelegate { + func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) { + Task { + try? await rejectNotificationRequest(request) + completion() + } + } + + func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) { + Task { + try? await rejectNotificationRequest(request) + completion() + } + } } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index 5a8f77349f..f259070705 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -11,7 +11,7 @@ import CoreDataStack import MastodonCore import MastodonLocalization -final class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { +class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { weak var context: AppContext! weak var coordinator: SceneCoordinator! From b3bfa5101b14fd3351d470ee401a22642e664ff2 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 12:19:20 +0200 Subject: [PATCH 41/48] Sprinkle in some, you guessed it: Localization (IOS-241) --- Localization/StringsConvertor/input/Base.lproj/app.json | 6 ++++-- Localization/app.json | 6 ++++-- .../Cell/NotificationFilteringBannerTableViewCell.swift | 2 +- .../AccountNotificationTimelineViewController.swift | 5 +++-- .../Requests/NotificationRequestTableViewCell.swift | 6 ++---- .../NotificationRequestsTableViewController.swift | 2 +- .../Sources/MastodonLocalization/Generated/Strings.swift | 8 ++++++-- .../Resources/Base.lproj/Localizable.strings | 4 +++- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index d328f00a69..a799483709 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -753,8 +753,10 @@ "suspend": "Your account has been suspended.", "learn_more": "Learn More" }, - "filtered_notification_banner": { - "title": "Filtered Notifications" + "filtered_notification": { + "title": "Filtered Notifications", + "accept": "Accept", + "dismiss": "Dismiss", }, "policy": { "title": "Filter Notifications from…", diff --git a/Localization/app.json b/Localization/app.json index d328f00a69..a799483709 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -753,8 +753,10 @@ "suspend": "Your account has been suspended.", "learn_more": "Learn More" }, - "filtered_notification_banner": { - "title": "Filtered Notifications" + "filtered_notification": { + "title": "Filtered Notifications", + "accept": "Accept", + "dismiss": "Dismiss", }, "policy": { "title": "Filter Notifications from…", diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index e24ddd087b..7e65c0754a 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -30,7 +30,7 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { iconImageWrapperView.addSubview(iconImageView) titleLabel = UILabel() - titleLabel.text = L10n.Scene.Notification.FilteredNotificationBanner.title + titleLabel.text = L10n.Scene.Notification.FilteredNotification.title titleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) subtitleLabel = UILabel() diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift index 0118c25e29..dc0f946b5e 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift @@ -3,6 +3,7 @@ import UIKit import MastodonCore import MastodonSDK +import MastodonLocalization protocol AccountNotificationTimelineViewControllerDelegate: AnyObject { func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) @@ -29,7 +30,7 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl //TODO: Localization func menu() -> UIMenu { let menu = UIMenu(children: [ - UIAction(title: "Accept", image: UIImage(systemName: "checkmark")) { [weak self] _ in + UIAction(title: L10n.Scene.Notification.FilteredNotification.accept, image: UIImage(systemName: "checkmark")) { [weak self] _ in guard let self else { return } coordinator.showLoading() @@ -38,7 +39,7 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl } coordinator.hideLoading() }, - UIAction(title: "Dismiss", image: UIImage(systemName: "speaker.slash")) { [weak self] _ in + UIAction(title: L10n.Scene.Notification.FilteredNotification.dismiss, image: UIImage(systemName: "speaker.slash")) { [weak self] _ in guard let self else { return } coordinator.showLoading() diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index 95b23e5066..ef60a00a21 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -57,8 +57,7 @@ class NotificationRequestTableViewCell: UITableViewCell { acceptNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false acceptNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) acceptNotificationRequestButton.setTitleColor(.white, for: .normal) - //TODO: Localization - acceptNotificationRequestButton.setTitle("Accept", for: .normal) + acceptNotificationRequestButton.setTitle(L10n.Scene.Notification.FilteredNotification.accept, for: .normal) acceptNotificationRequestButton.setImage(UIImage(systemName: "checkmark"), for: .normal) acceptNotificationRequestButton.imageView?.contentMode = .scaleAspectFit acceptNotificationRequestButton.setBackgroundImage(.placeholder(color: Asset.Scene.Notification.confirmFollowRequestButtonBackground.color), for: .normal) @@ -83,8 +82,7 @@ class NotificationRequestTableViewCell: UITableViewCell { rejectNotificationRequestButton.translatesAutoresizingMaskIntoConstraints = false rejectNotificationRequestButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) rejectNotificationRequestButton.setTitleColor(.black, for: .normal) - //TODO: Localization - rejectNotificationRequestButton.setTitle("Dismiss", for: .normal) + rejectNotificationRequestButton.setTitle(L10n.Scene.Notification.FilteredNotification.dismiss, for: .normal) rejectNotificationRequestButton.setImage(UIImage(systemName: "speaker.slash"), for: .normal) rejectNotificationRequestButton.imageView?.contentMode = .scaleAspectFit rejectNotificationRequestButton.setInsets(forContentPadding: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8), imageTitlePadding: 8) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index 597535aeb0..edb8940c38 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -59,7 +59,7 @@ class NotificationRequestsTableViewController: UIViewController, NeedsDependency tableView.delegate = self self.dataSource = dataSource - title = L10n.Scene.Notification.FilteredNotificationBanner.title + title = L10n.Scene.Notification.FilteredNotification.title } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 509431c5f7..ef5edbfdb2 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -895,9 +895,13 @@ public enum L10n { } } public enum Notification { - public enum FilteredNotificationBanner { + public enum FilteredNotification { + /// Accept + public static let accept = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Accept", fallback: "Accept") + /// Dismiss + public static let dismiss = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Dismiss", fallback: "Dismiss") /// Filtered Notifications - public static let title = L10n.tr("Localizable", "Scene.Notification.FilteredNotificationBanner.Title", fallback: "Filtered Notifications") + public static let title = L10n.tr("Localizable", "Scene.Notification.FilteredNotification.Title", fallback: "Filtered Notifications") } public enum FollowRequest { /// Accept diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 0c7b4bc42a..0e559e9395 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -316,7 +316,9 @@ uploaded to Mastodon."; "Scene.Login.ServerSearchField.Placeholder" = "Enter URL or search for your server"; "Scene.Login.Subtitle" = "Log in with the server where you created your account."; "Scene.Login.Title" = "Welcome Back"; -"Scene.Notification.FilteredNotificationBanner.Title" = "Filtered Notifications"; +"Scene.Notification.FilteredNotification.Accept" = "Accept"; +"Scene.Notification.FilteredNotification.Dismiss" = "Dismiss"; +"Scene.Notification.FilteredNotification.Title" = "Filtered Notifications"; "Scene.Notification.FollowRequest.Accept" = "Accept"; "Scene.Notification.FollowRequest.Accepted" = "Accepted"; "Scene.Notification.FollowRequest.Reject" = "reject"; From 86aa92da0bb97eb59a5216f61904daadafc5f172 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 12:34:50 +0200 Subject: [PATCH 42/48] Cleanup (IOS-241) --- ...ficationFilteringBannerTableViewCell.swift | 1 - ...ntNotificationTimelineViewController.swift | 13 ++-- ...ificationRequestsTableViewController.swift | 61 ++++++++++--------- 3 files changed, 35 insertions(+), 40 deletions(-) diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index 7e65c0754a..efa2874f9b 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -37,7 +37,6 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { subtitleLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) subtitleLabel.textColor = .secondaryLabel - labelStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) labelStackView.translatesAutoresizingMaskIntoConstraints = false labelStackView.alignment = .leading diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift index dc0f946b5e..70822f083e 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift @@ -6,8 +6,8 @@ import MastodonSDK import MastodonLocalization protocol AccountNotificationTimelineViewControllerDelegate: AnyObject { - func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) - func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) + func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest) + func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: Mastodon.Entity.NotificationRequest) } class AccountNotificationTimelineViewController: NotificationTimelineViewController { @@ -27,25 +27,20 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl // MARK: - Actions - //TODO: Localization func menu() -> UIMenu { let menu = UIMenu(children: [ UIAction(title: L10n.Scene.Notification.FilteredNotification.accept, image: UIImage(systemName: "checkmark")) { [weak self] _ in guard let self else { return } coordinator.showLoading() - self.delegate?.acceptRequest(self, request: request) { - self.navigationController?.popViewController(animated: true) - } + self.delegate?.acceptRequest(self, request: request) coordinator.hideLoading() }, UIAction(title: L10n.Scene.Notification.FilteredNotification.dismiss, image: UIImage(systemName: "speaker.slash")) { [weak self] _ in guard let self else { return } coordinator.showLoading() - self.delegate?.dismissRequest(self, request: request) { - self.navigationController?.popViewController(animated: true) - } + self.delegate?.dismissRequest(self, request: request) coordinator.hideLoading() } ]) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index edb8940c38..ad0132f8d0 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -124,30 +124,7 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC Task { [weak self] in guard let self else { return } do { - _ = try await context.apiService.acceptNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, - id: notificationRequest.id) - - let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value - - NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) - - if requests.count > 0 { - - await MainActor.run { [weak self] in - guard let self else { return } - self.viewModel.requests = requests - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) - - self.dataSource?.apply(snapshot) - } - } else { - await MainActor.run { [weak self] in - _ = self?.navigationController?.popViewController(animated: true) - } - } - + try await acceptNotificationRequest(notificationRequest) } catch { cell.acceptNotificationRequestActivityIndicatorView.stopAnimating() cell.acceptNotificationRequestButton.tintColor = .white @@ -157,7 +134,33 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC } } } - + + private func acceptNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws { + _ = try await context.apiService.acceptNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, + id: notificationRequest.id) + + let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value + + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) + + if requests.count > 0 { + await MainActor.run { [weak self] in + guard let self else { return } + self.viewModel.requests = requests + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + self.dataSource?.apply(snapshot) + } + } else { + await MainActor.run { [weak self] in + _ = self?.navigationController?.popViewController(animated: true) + } + } + + } + func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { cell.rejectNotificationRequestActivityIndicatorView.isHidden = false @@ -209,17 +212,15 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC } extension NotificationRequestsTableViewController: AccountNotificationTimelineViewControllerDelegate { - func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) { + func acceptRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest) { Task { - try? await rejectNotificationRequest(request) - completion() + try? await acceptNotificationRequest(request) } } - func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest, completion: @escaping (() -> Void)) { + func dismissRequest(_ viewController: AccountNotificationTimelineViewController, request: MastodonSDK.Mastodon.Entity.NotificationRequest) { Task { try? await rejectNotificationRequest(request) - completion() } } } From 2d059455d816c1ebb7dc0e7de6dadf1c0921cfe2 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 14:29:05 +0200 Subject: [PATCH 43/48] Add chevron (IOS-241) --- .../Requests/NotificationRequestTableViewCell.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift index ef60a00a21..e84574820b 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -23,6 +23,7 @@ class NotificationRequestTableViewCell: UITableViewCell { let nameLabel: MetaLabel let usernameLabel: MetaLabel let avatarButton: AvatarButton + let chevronImageView: UIImageView private let labelStackView: UIStackView private let avatarStackView: UIStackView @@ -109,7 +110,10 @@ class NotificationRequestTableViewCell: UITableViewCell { buttonStackView.spacing = 16 buttonStackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0) // set bottom padding - avatarStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView]) + chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) + chevronImageView.tintColor = .tertiaryLabel + + avatarStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView, UIView(), chevronImageView]) avatarStackView.axis = .horizontal avatarStackView.alignment = .center avatarStackView.spacing = 12 From 1bf763a94cfdde80f32ea4edb613a78efaeef5a8 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 15:05:24 +0200 Subject: [PATCH 44/48] Add an english translation (IOS-241) For whatever reason the plural-translation doesn't have a fallback to Base?! --- .../en.lproj/Localizable.stringsdict | 972 +++++++++--------- 1 file changed, 494 insertions(+), 478 deletions(-) diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict index 2b09ee0040..c97236f967 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.stringsdict @@ -1,481 +1,497 @@ - + - - a11y.plural.count.unread.notification - - NSStringLocalizedFormatKey - %#@notification_count_unread_notification@ - notification_count_unread_notification - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 unread notification - other - %ld unread notifications - - - a11y.plural.count.input_limit_exceeds - - NSStringLocalizedFormatKey - Input limit exceeds %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 character - other - %ld characters - - - a11y.plural.count.input_limit_remains - - NSStringLocalizedFormatKey - Input limit remains %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 character - other - %ld characters - - - a11y.plural.count.characters_left - - NSStringLocalizedFormatKey - %#@character_count@ - character_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 character left - other - %ld characters left - - - plural.count.followed_by_and_mutual - - NSStringLocalizedFormatKey - %#@names@%#@count_mutual@ - names - - one - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - other - - - count_mutual - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - Followed by %1$@, and another mutual - other - Followed by %1$@, and %ld mutuals - - - plural.count.metric_formatted.post - - NSStringLocalizedFormatKey - %@ %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - post - other - posts - - - plural.count.media - - NSStringLocalizedFormatKey - %#@media_count@ - media_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 media - other - %ld media - - - plural.count.post - - NSStringLocalizedFormatKey - %#@post_count@ - post_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 post - other - %ld posts - - - plural.count.favorite - - NSStringLocalizedFormatKey - %#@favorite_count@ - favorite_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 favorite - other - %ld favorites - - - plural.count.reblog - - NSStringLocalizedFormatKey - %#@reblog_count@ - reblog_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 reblog - other - %ld reblogs - - - plural.count.reblog_a11y - - NSStringLocalizedFormatKey - %#@reblog_count@ - reblog_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 re-blog - other - %ld re-blogs - - - plural.count.reply - - NSStringLocalizedFormatKey - %#@reply_count@ - reply_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 reply - other - %ld replies - - - plural.count.vote - - NSStringLocalizedFormatKey - %#@vote_count@ - vote_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 vote - other - %ld votes - - - plural.count.voter - - NSStringLocalizedFormatKey - %#@voter_count@ - voter_count - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 voter - other - %ld voters - - - plural.people_talking - - NSStringLocalizedFormatKey - %#@count_people_talking@ - count_people_talking - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 people talking - other - %ld people talking - - - plural.count.following - - NSStringLocalizedFormatKey - %#@count_following@ - count_following - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 following - other - %ld following - - - plural.count.follower - - NSStringLocalizedFormatKey - %#@count_follower@ - count_follower - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 follower - other - %ld followers - - - date.year.left - - NSStringLocalizedFormatKey - %#@count_year_left@ - count_year_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 year left - other - %ld years left - - - date.month.left - - NSStringLocalizedFormatKey - %#@count_month_left@ - count_month_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 months left - other - %ld months left - - - date.day.left - - NSStringLocalizedFormatKey - %#@count_day_left@ - count_day_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 day left - other - %ld days left - - - date.hour.left - - NSStringLocalizedFormatKey - %#@count_hour_left@ - count_hour_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 hour left - other - %ld hours left - - - date.minute.left - - NSStringLocalizedFormatKey - %#@count_minute_left@ - count_minute_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 minute left - other - %ld minutes left - - - date.second.left - - NSStringLocalizedFormatKey - %#@count_second_left@ - count_second_left - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1 second left - other - %ld seconds left - - - date.year.ago.abbr - - NSStringLocalizedFormatKey - %#@count_year_ago_abbr@ - count_year_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1y ago - other - %ldy ago - - - date.month.ago.abbr - - NSStringLocalizedFormatKey - %#@count_month_ago_abbr@ - count_month_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1M ago - other - %ldM ago - - - date.day.ago.abbr - - NSStringLocalizedFormatKey - %#@count_day_ago_abbr@ - count_day_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1d ago - other - %ldd ago - - - date.hour.ago.abbr - - NSStringLocalizedFormatKey - %#@count_hour_ago_abbr@ - count_hour_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1h ago - other - %ldh ago - - - date.minute.ago.abbr - - NSStringLocalizedFormatKey - %#@count_minute_ago_abbr@ - count_minute_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1m ago - other - %ldm ago - - - date.second.ago.abbr - - NSStringLocalizedFormatKey - %#@count_second_ago_abbr@ - count_second_ago_abbr - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - ld - one - 1s ago - other - %lds ago - - - + + a11y.plural.count.unread.notification + + NSStringLocalizedFormatKey + %#@notification_count_unread_notification@ + notification_count_unread_notification + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 unread notification + other + %ld unread notifications + + + a11y.plural.count.input_limit_exceeds + + NSStringLocalizedFormatKey + Input limit exceeds %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + + a11y.plural.count.input_limit_remains + + NSStringLocalizedFormatKey + Input limit remains %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character + other + %ld characters + + + a11y.plural.count.characters_left + + NSStringLocalizedFormatKey + %#@character_count@ + character_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 character left + other + %ld characters left + + + plural.count.followed_by_and_mutual + + NSStringLocalizedFormatKey + %#@names@%#@count_mutual@ + names + + one + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + other + + + count_mutual + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + Followed by %1$@, and another mutual + other + Followed by %1$@, and %ld mutuals + + + plural.count.metric_formatted.post + + NSStringLocalizedFormatKey + %@ %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + post + other + posts + + + plural.count.media + + NSStringLocalizedFormatKey + %#@media_count@ + media_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 media + other + %ld media + + + plural.count.post + + NSStringLocalizedFormatKey + %#@post_count@ + post_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 post + other + %ld posts + + + plural.count.favorite + + NSStringLocalizedFormatKey + %#@favorite_count@ + favorite_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 favorite + other + %ld favorites + + + plural.count.reblog + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reblog + other + %ld reblogs + + + plural.count.reblog_a11y + + NSStringLocalizedFormatKey + %#@reblog_count@ + reblog_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 re-blog + other + %ld re-blogs + + + plural.count.reply + + NSStringLocalizedFormatKey + %#@reply_count@ + reply_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 reply + other + %ld replies + + + plural.count.vote + + NSStringLocalizedFormatKey + %#@vote_count@ + vote_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 vote + other + %ld votes + + + plural.count.voter + + NSStringLocalizedFormatKey + %#@voter_count@ + voter_count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 voter + other + %ld voters + + + plural.people_talking + + NSStringLocalizedFormatKey + %#@count_people_talking@ + count_people_talking + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 people talking + other + %ld people talking + + + plural.count.following + + NSStringLocalizedFormatKey + %#@count_following@ + count_following + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 following + other + %ld following + + + plural.count.follower + + NSStringLocalizedFormatKey + %#@count_follower@ + count_follower + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 follower + other + %ld followers + + + date.year.left + + NSStringLocalizedFormatKey + %#@count_year_left@ + count_year_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 year left + other + %ld years left + + + date.month.left + + NSStringLocalizedFormatKey + %#@count_month_left@ + count_month_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 months left + other + %ld months left + + + date.day.left + + NSStringLocalizedFormatKey + %#@count_day_left@ + count_day_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 day left + other + %ld days left + + + date.hour.left + + NSStringLocalizedFormatKey + %#@count_hour_left@ + count_hour_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 hour left + other + %ld hours left + + + date.minute.left + + NSStringLocalizedFormatKey + %#@count_minute_left@ + count_minute_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 minute left + other + %ld minutes left + + + date.second.left + + NSStringLocalizedFormatKey + %#@count_second_left@ + count_second_left + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1 second left + other + %ld seconds left + + + date.year.ago.abbr + + NSStringLocalizedFormatKey + %#@count_year_ago_abbr@ + count_year_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1y ago + other + %ldy ago + + + date.month.ago.abbr + + NSStringLocalizedFormatKey + %#@count_month_ago_abbr@ + count_month_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1M ago + other + %ldM ago + + + date.day.ago.abbr + + NSStringLocalizedFormatKey + %#@count_day_ago_abbr@ + count_day_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1d ago + other + %ldd ago + + + date.hour.ago.abbr + + NSStringLocalizedFormatKey + %#@count_hour_ago_abbr@ + count_hour_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1h ago + other + %ldh ago + + + date.minute.ago.abbr + + NSStringLocalizedFormatKey + %#@count_minute_ago_abbr@ + count_minute_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1m ago + other + %ldm ago + + + date.second.ago.abbr + + NSStringLocalizedFormatKey + %#@count_second_ago_abbr@ + count_second_ago_abbr + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + 1s ago + other + %lds ago + + + plural.filtered_notification_banner.subtitle + + NSStringLocalizedFormatKey + %#@number_of_requests@ + number_of_requests + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + ld + one + One person you may know + other + %ld people you may know + + + From 9da5b99b28046a804c44d2c8c37ea6f7ccecceff Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 16:01:25 +0200 Subject: [PATCH 45/48] Show modal on ipad (IOS-241) --- .../Provider/DataSourceFacade+Notifications.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index f6dac507cc..7abca3694d 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -17,7 +17,15 @@ extension DataSourceFacade { provider.coordinator.hideLoading() - provider.coordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: .show) + let transition: SceneCoordinator.Transition + + if provider.traitCollection.userInterfaceIdiom == .phone { + transition = .show + } else { + transition = .modal(animated: true) + } + + provider.coordinator.present(scene: .notificationRequests(viewModel: viewModel), transition: transition) } catch { //TODO: Error Handling provider.coordinator.hideLoading() From 8d535be84a2f34d3d60e55eb841dccf392982546 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 16:20:23 +0200 Subject: [PATCH 46/48] Fix bottom margins as requested (IOS-241) --- .../NotificationView+Configuration.swift | 4 ++-- .../NotificationView/NotificationView.swift | 10 +++++----- .../View/Control/ActionToolbarContainer.swift | 16 ---------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift index 01ae0ab10f..b296bcd902 100644 --- a/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift +++ b/Mastodon/Scene/Notification/NotificationView/NotificationView+Configuration.swift @@ -39,9 +39,9 @@ extension NotificationView { switch notification.entity.type { case .follow: - setAuthorContainerBottomPaddingViewDisplay() + setAuthorContainerBottomPaddingViewDisplay(isHidden: true) case .followRequest: - setFollowRequestAdaptiveMarginContainerViewDisplay() + setFollowRequestAdaptiveMarginContainerViewDisplay(isHidden: true) case .mention, .status: if let status = notification.status { statusView.configure(status: status) diff --git a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift index cf330350b4..4da3ab81d8 100644 --- a/Mastodon/Scene/Notification/NotificationView/NotificationView.swift +++ b/Mastodon/Scene/Notification/NotificationView/NotificationView.swift @@ -210,7 +210,7 @@ extension NotificationView { containerStackView.topAnchor.constraint(equalTo: topAnchor), containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), containerStackView.trailingAnchor.constraint(equalTo: trailingAnchor), - bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), + bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor, constant: 8), ]) // author container: H - [ avatarButton | author meta container ] @@ -420,12 +420,12 @@ extension NotificationView { extension NotificationView { - public func setAuthorContainerBottomPaddingViewDisplay() { - authorContainerViewBottomPaddingView.isHidden = false + public func setAuthorContainerBottomPaddingViewDisplay(isHidden: Bool = false) { + authorContainerViewBottomPaddingView.isHidden = isHidden } - public func setFollowRequestAdaptiveMarginContainerViewDisplay() { - followRequestAdaptiveMarginContainerView.isHidden = false + public func setFollowRequestAdaptiveMarginContainerViewDisplay(isHidden: Bool = false) { + followRequestAdaptiveMarginContainerView.isHidden = isHidden } public func setStatusViewDisplay() { diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift index eba7e1672f..2de73dc5b5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ActionToolbarContainer.swift @@ -290,19 +290,3 @@ extension ActionToolbarContainer { set { } } } - -#if DEBUG -import SwiftUI - -struct ActionToolbarContainer_Previews: PreviewProvider { - static var previews: some View { - Group { - UIViewPreview(width: 300) { - ActionToolbarContainer() - } - .previewLayout(.fixed(width: 300, height: 44)) - .previewDisplayName("Inline") - } - } -} -#endif From 766e6312173d266a0ebcffeeea2ed7d86352f1bf Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 24 Jul 2024 16:37:33 +0200 Subject: [PATCH 47/48] Update policy after changes --- .../AccountNotificationTimelineViewController.swift | 2 ++ .../NotificationTimelineViewController.swift | 3 +++ .../NotificationTimelineViewModel.swift | 6 ++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift index 70822f083e..e53886a4a5 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/Account Notifications/AccountNotificationTimelineViewController.swift @@ -33,6 +33,7 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl guard let self else { return } coordinator.showLoading() + self.navigationController?.popViewController(animated: true) self.delegate?.acceptRequest(self, request: request) coordinator.hideLoading() }, @@ -40,6 +41,7 @@ class AccountNotificationTimelineViewController: NotificationTimelineViewControl guard let self else { return } coordinator.showLoading() + self.navigationController?.popViewController(animated: true) self.delegate?.dismissRequest(self, request: request) coordinator.hideLoading() } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index f259070705..60906a15a4 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -126,6 +126,9 @@ extension NotificationTimelineViewController { @objc private func refreshControlValueChanged(_ sender: RefreshControl) { Task { + let policy = try? await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox) + viewModel.notificationPolicy = policy?.value + await viewModel.loadLatest() } } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index a60f48025a..707cc2173f 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -21,7 +21,7 @@ final class NotificationTimelineViewModel { let context: AppContext let authContext: AuthContext let scope: Scope - let notificationPolicy: Mastodon.Entity.NotificationPolicy? + var notificationPolicy: Mastodon.Entity.NotificationPolicy? let dataController: FeedDataController @Published var isLoadingLatest = false @Published var lastAutomaticFetchTimestamp: Date? @@ -96,8 +96,10 @@ final class NotificationTimelineViewModel { //MARK: - Notifications @objc func notificationFilteringChanged(_ notification: Notification) { - dataController.records = [] Task { [weak self] in + let policy = try await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox) + self?.notificationPolicy = policy.value + await self?.loadLatest() } } From bac8c23d2ccdf124d9861a3261b4aecfbdeaee31 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 26 Jul 2024 11:08:28 +0200 Subject: [PATCH 48/48] Adress feedback (IOS-241) --- ...er+NotificationTableViewCellDelegate.swift | 3 +- ...Provider+StatusTableViewCellDelegate.swift | 3 +- ...ficationFilteringBannerTableViewCell.swift | 1 - ...ificationRequestsTableViewController.swift | 40 +++++++++---------- .../NotificationTimelineViewModel.swift | 10 +++-- .../Sources/MastodonSDK/Query/Query.swift | 2 +- 6 files changed, 29 insertions(+), 30 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index 5d23083b85..1290953f72 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -515,7 +515,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut case .account(let account, _): await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) case .notification, .hashtag(_), .notificationBanner(_): - print("TODO") + // not supposed to happen + break } } // end Task } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index 24ad78b286..9db7ac73d7 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -619,7 +619,8 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte account: account ) case .notification, .hashtag(_), .notificationBanner(_): - assertionFailure("TODO") + // not supposed to happen + break } } } diff --git a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift index efa2874f9b..2fd02878d7 100644 --- a/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift +++ b/Mastodon/Scene/Notification/Cell/NotificationFilteringBannerTableViewCell.swift @@ -63,7 +63,6 @@ class NotificationFilteringBannerTableViewCell: UITableViewCell { private func setupConstraints() { let constraints = [ - iconImageWrapperView.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width), iconImageWrapperView.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.defaultHigh), iconImageView.centerXAnchor.constraint(equalTo: iconImageWrapperView.centerXAnchor), diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift index ad0132f8d0..8befab7c75 100644 --- a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -137,28 +137,26 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC private func acceptNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws { _ = try await context.apiService.acceptNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, - id: notificationRequest.id) + id: notificationRequest.id) let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) - if requests.count > 0 { - await MainActor.run { [weak self] in - guard let self else { return } + await MainActor.run { [weak self] in + guard let self else { return } + + if requests.count > 0 { self.viewModel.requests = requests var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) self.dataSource?.apply(snapshot) - } - } else { - await MainActor.run { [weak self] in - _ = self?.navigationController?.popViewController(animated: true) + } else { + _ = self.navigationController?.popViewController(animated: true) } } - } func rejectNotificationRequest(_ cell: NotificationRequestTableViewCell, notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) { @@ -183,31 +181,29 @@ extension NotificationRequestsTableViewController: NotificationRequestTableViewC } } } - + private func rejectNotificationRequest(_ notificationRequest: MastodonSDK.Mastodon.Entity.NotificationRequest) async throws { _ = try await context.apiService.rejectNotificationRequests(authenticationBox: authContext.mastodonAuthenticationBox, id: notificationRequest.id) - + let requests = try await context.apiService.notificationRequests(authenticationBox: authContext.mastodonAuthenticationBox).value - + NotificationCenter.default.post(name: .notificationFilteringChanged, object: nil) - - if requests.count > 0 { - await MainActor.run { [weak self] in - guard let self else { return } + + await MainActor.run { [weak self] in + guard let self else { return } + + if requests.count > 0 { self.viewModel.requests = requests var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(self.viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) - + self.dataSource?.apply(snapshot) - } - } else { - await MainActor.run { [weak self] in - _ = self?.navigationController?.popViewController(animated: true) + } else { + _ = self.navigationController?.popViewController(animated: true) } } - } } diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 707cc2173f..2f8d9a6b5a 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -84,7 +84,7 @@ final class NotificationTimelineViewModel { case .mentions: FileManager.default.cacheNotificationsMentions(items: items, for: authContext.mastodonAuthenticationBox) case .fromAccount(_): - //TODO: we don't persist these + //NOTE: we don't persist these break } }) @@ -97,10 +97,12 @@ final class NotificationTimelineViewModel { @objc func notificationFilteringChanged(_ notification: Notification) { Task { [weak self] in - let policy = try await context.apiService.notificationPolicy(authenticationBox: authContext.mastodonAuthenticationBox) - self?.notificationPolicy = policy.value + guard let self else { return } - await self?.loadLatest() + let policy = try await self.context.apiService.notificationPolicy(authenticationBox: self.authContext.mastodonAuthenticationBox) + self.notificationPolicy = policy.value + + await self.loadLatest() } } } diff --git a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift index caaf6a02ab..ba3ec63cd2 100644 --- a/MastodonSDK/Sources/MastodonSDK/Query/Query.swift +++ b/MastodonSDK/Sources/MastodonSDK/Query/Query.swift @@ -56,7 +56,7 @@ extension PostQuery { protocol PatchQuery: RequestQuery { } extension PatchQuery { - // By default a `PostQuery` does not have query items + // By default a `PatchQuery` does not have query items var queryItems: [URLQueryItem]? { nil } }