diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 71c55e9a3..d84fbabe7 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -850,6 +850,8 @@ D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; DFFB8FC2279B75E300AC21D7 /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + DFFC199827A0D0D7004B7AEF /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFC199727A0D0D7004B7AEF /* NotificationsViewController.swift */; }; + DFFC199A27A0D32A004B7AEF /* NotificationsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFC199927A0D32A004B7AEF /* NotificationsTableViewCell.swift */; }; FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; }; FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; @@ -1588,6 +1590,8 @@ D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebFeed+Scriptability.swift"; sourceTree = ""; }; D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = ""; }; + DFFC199727A0D0D7004B7AEF /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = ""; }; + DFFC199927A0D32A004B7AEF /* NotificationsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewCell.swift; sourceTree = ""; }; FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = ""; }; FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorter.swift; sourceTree = ""; }; FFD43E372340F320009E5CA3 /* MarkAsReadAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkAsReadAlertController.swift; sourceTree = ""; }; @@ -1984,6 +1988,8 @@ 51A16993235E10D600EB091F /* SettingsViewController.swift */, 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */, 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */, + DFFC199727A0D0D7004B7AEF /* NotificationsViewController.swift */, + DFFC199927A0D32A004B7AEF /* NotificationsTableViewCell.swift */, ); path = Settings; sourceTree = ""; @@ -4040,6 +4046,7 @@ 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, B2B80778239C4C7000F191E0 /* RSImage-AppIcons.swift in Sources */, 518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */, + DFFC199827A0D0D7004B7AEF /* NotificationsViewController.swift in Sources */, 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, @@ -4051,6 +4058,7 @@ 51C45291226509C800C03939 /* SmartFeed.swift in Sources */, 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 511B9807237DCAC90028BCAA /* UserInfoKey.swift in Sources */, + DFFC199A27A0D32A004B7AEF /* NotificationsTableViewCell.swift in Sources */, 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* WebViewProvider.swift in Sources */, diff --git a/iOS/Settings/NotificationsTableViewCell.swift b/iOS/Settings/NotificationsTableViewCell.swift new file mode 100644 index 000000000..7b1a07d4b --- /dev/null +++ b/iOS/Settings/NotificationsTableViewCell.swift @@ -0,0 +1,70 @@ +// +// NotificationsTableViewCell.swift +// NetNewsWire-iOS +// +// Created by Stuart Breckenridge on 26/01/2022. +// Copyright © 2022 Ranchero Software. All rights reserved. +// + +import UIKit +import Account +import UserNotifications + +class NotificationsTableViewCell: UITableViewCell { + + @IBOutlet weak var notificationsSwitch: UISwitch! + @IBOutlet weak var notificationsLabel: UILabel! + @IBOutlet weak var notificationsImageView: UIImageView! + var feed: WebFeed? + + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + func configure(_ webFeed: WebFeed, _ status: UNAuthorizationStatus) { + self.feed = webFeed + var isOn = false + if webFeed.isNotifyAboutNewArticles == nil { + isOn = false + } else { + isOn = webFeed.isNotifyAboutNewArticles! + } + notificationsSwitch.isOn = isOn + notificationsSwitch.addTarget(self, action: #selector(toggleWebFeedNotification(_:)), for: .touchUpInside) + if status == .denied { notificationsSwitch.isEnabled = false } + notificationsLabel.text = webFeed.nameForDisplay + notificationsImageView.image = webFeed.smallIcon?.image + notificationsImageView.layer.cornerRadius = 4 + } + + func configure(_ status: UNAuthorizationStatus) { + notificationsSwitch.isOn = (status == .authorized) ? true : false + if status == .denied { notificationsSwitch.isEnabled = false } + notificationsLabel.text = NSLocalizedString("Enable Notifications", comment: "") + notificationsImageView.image = UIImage(systemName: "bell") + notificationsImageView.layer.cornerRadius = 4 + } + + @objc + private func toggleWebFeedNotification(_ sender: Any) { + guard let feed = feed else { + return + } + if feed.isNotifyAboutNewArticles == nil { + feed.isNotifyAboutNewArticles = true + } + else { + feed.isNotifyAboutNewArticles!.toggle() + } + } + + +} diff --git a/iOS/Settings/NotificationsViewController.swift b/iOS/Settings/NotificationsViewController.swift new file mode 100644 index 000000000..e309c60bb --- /dev/null +++ b/iOS/Settings/NotificationsViewController.swift @@ -0,0 +1,98 @@ +// +// NotificationsViewController.swift +// NetNewsWire-iOS +// +// Created by Stuart Breckenridge on 26/01/2022. +// Copyright © 2022 Ranchero Software. All rights reserved. +// + +import UIKit +import Account +import UserNotifications + +class NotificationsViewController: UIViewController { + + @IBOutlet weak var notificationsTableView: UITableView! + private var status: UNAuthorizationStatus = .notDetermined + + + override func viewDidLoad() { + super.viewDidLoad() + self.title = NSLocalizedString("Notifications", comment: "Notifications") + notificationsTableView.sectionHeaderTopPadding = 15 + UNUserNotificationCenter.current().getNotificationSettings { settings in + DispatchQueue.main.async { + self.status = settings.authorizationStatus + self.notificationsTableView.reloadData() + } + } + NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) + } + + @objc + private func faviconDidBecomeAvailable(_ sender: Any) { + notificationsTableView.reloadData() + } + + @objc + private func webFeedIconDidBecomeAvailable(_ sender: Any) { + notificationsTableView.reloadData() + } + + private func sortedWebFeedsForAccount(_ account: Account) -> [WebFeed] { + return Array(account.flattenedWebFeeds()).sorted(by: { $0.nameForDisplay.caseInsensitiveCompare($1.nameForDisplay) == .orderedAscending }) + } + +} + +// MARK: UITableViewDataSource +extension NotificationsViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + /// 1 section for enabling notifications + /// + number of active accounts + return 1 + AccountManager.shared.activeAccounts.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { return 1 } + return AccountManager.shared.sortedActiveAccounts[section - 1].flattenedWebFeeds().count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationsCell") as! NotificationsTableViewCell + + if indexPath.section == 0 { + cell.configure(status) + } else { + let account = AccountManager.shared.sortedActiveAccounts[indexPath.section - 1] + let feed = sortedWebFeedsForAccount(account)[indexPath.row] + cell.configure(feed, status) + } + + return cell + } + + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section == 0 { return NSLocalizedString("Enable Notifications", comment: "Enable Notifications") } + return AccountManager.shared.sortedActiveAccounts[section - 1].nameForDisplay + } + + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + if section == 0 { + if status == .denied { + return NSLocalizedString("Notification permissions are currently denied. Enable notifications in the Settings app.", comment: "Notifications denied.") + } + } + return nil + } +} + +extension NotificationsViewController: UITableViewDelegate { + + + + +} diff --git a/iOS/Settings/Settings.storyboard b/iOS/Settings/Settings.storyboard index e36af8d35..e3e7f3427 100644 --- a/iOS/Settings/Settings.storyboard +++ b/iOS/Settings/Settings.storyboard @@ -1,9 +1,8 @@ - + - - + @@ -21,14 +20,31 @@ - + - + + + + + + + + + + + +